parent
aea5c1d0ea
commit
f430ef1049
|
@ -0,0 +1,446 @@
|
|||
import datetime
|
||||
from datetime import timezone
|
||||
from dateutil.tz import tzutc
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from ephemetoot import ephemetoot
|
||||
|
||||
|
||||
########################
|
||||
# MOCKS #
|
||||
########################
|
||||
|
||||
toot_dict = {
|
||||
'id': 104136090490756999,
|
||||
'created_at': datetime.datetime(2020, 5, 9, 2, 17, 18, 598000, tzinfo=tzutc()),
|
||||
'in_reply_to_id': None,
|
||||
'in_reply_to_account_id': None,
|
||||
'sensitive': False,
|
||||
'spoiler_text': '',
|
||||
'visibility': 'public',
|
||||
'language': 'en',
|
||||
'uri': 'https://example.social/users/testbot/statuses/104136090490756503',
|
||||
'url': 'https://example.social/@testbot/104136090490756503',
|
||||
'replies_count': 0,
|
||||
'reblogs_count': 0,
|
||||
'favourites_count': 0,
|
||||
'favourited': False,
|
||||
'reblogged': False,
|
||||
'muted': False,
|
||||
'bookmarked': False,
|
||||
'pinned': True,
|
||||
'content': '<p>hello I am testing</p>',
|
||||
'reblog': None,
|
||||
'application': None,
|
||||
'account': {
|
||||
'id': 16186,
|
||||
'username': 'testbot',
|
||||
'acct': 'testbot',
|
||||
'display_name': 'ephemtoot Testing Bot',
|
||||
'locked': True,
|
||||
'bot': True,
|
||||
'discoverable': False,
|
||||
'group': False,
|
||||
'created_at': datetime.datetime(2018, 11, 16, 23, 15, 15, 718000, tzinfo=tzutc()),
|
||||
'note': '<p>Liable to explode at any time, handle with care.</p>',
|
||||
'url': 'https://example.social/@testbot',
|
||||
'avatar': 'https://example.social/system/accounts/avatars/000/016/186/original/66d11c4191332e7a.png?1542410869',
|
||||
'avatar_static': 'https://example.social/system/accounts/avatars/000/016/186/original/66d11c4191332e7a.png?1542410869',
|
||||
'header': 'https://example.social/headers/original/header.png',
|
||||
'header_static': 'https://example.social/headers/original/header.png',
|
||||
'followers_count': 100,
|
||||
'following_count': 10,
|
||||
'statuses_count': 99,
|
||||
'last_status_at': datetime.datetime(2020, 8, 17, 0, 0),
|
||||
'emojis': [],
|
||||
'fields': [
|
||||
{
|
||||
'name': 'Fully',
|
||||
'value': 'Automated',
|
||||
'verified_at': None
|
||||
},
|
||||
{
|
||||
'name': 'Luxury',
|
||||
'value': 'Communism',
|
||||
'verified_at': None
|
||||
}
|
||||
]
|
||||
},
|
||||
'media_attachments': [],
|
||||
'mentions': [],
|
||||
'tags': [],
|
||||
'emojis': [],
|
||||
'card': None,
|
||||
'poll': None
|
||||
}
|
||||
|
||||
# Turn dict into object needed by mastodon.py
|
||||
# Use this in tests after making any changes
|
||||
# you need to your dict object
|
||||
# NOTE: ensure values in the dict object are what you need:
|
||||
# it can be mutated by any test before your test runs
|
||||
|
||||
def dict2obj(d):
|
||||
# checking whether object d is a
|
||||
# instance of class list
|
||||
if isinstance(d, list):
|
||||
d = [dict2obj(x) for x in d]
|
||||
|
||||
# if d is not a instance of dict then
|
||||
# directly object is returned
|
||||
if not isinstance(d, dict):
|
||||
return d
|
||||
|
||||
# declaring a class
|
||||
class C:
|
||||
pass
|
||||
|
||||
# constructor of the class passed to obj
|
||||
obj = C()
|
||||
|
||||
for k in d:
|
||||
obj.__dict__[k] = dict2obj(d[k])
|
||||
|
||||
return obj
|
||||
|
||||
# here is our toot object - use this in tests
|
||||
toot = dict2obj(toot_dict)
|
||||
|
||||
# config file after being parsed by yaml.safe_load
|
||||
config_file = {
|
||||
'access_token': 'abcd_1234',
|
||||
'username': 'alice',
|
||||
'base_url': 'test.social',
|
||||
'hashtags_to_keep': ['ephemetoot'],
|
||||
'days_to_keep': 14,
|
||||
'keep_pinned': True,
|
||||
'toots_to_keep': [103996285277439262, 103976473612749097, 103877521458738491],
|
||||
'visibility_to_keep': [None],
|
||||
'archive': 'archive'
|
||||
}
|
||||
|
||||
# mock GitHub API call for the version number
|
||||
class MockGitHub:
|
||||
@staticmethod
|
||||
def json():
|
||||
return {"tag_name": "vLATEST_VERSION"}
|
||||
|
||||
# mock Mastodon
|
||||
class Mocktodon:
|
||||
def __init__(self):
|
||||
return None
|
||||
|
||||
def status_delete(self, t=toot):
|
||||
return None
|
||||
|
||||
def status_unreblog(self, t=toot):
|
||||
return None
|
||||
|
||||
def ratelimit_remaining(self):
|
||||
return 100
|
||||
|
||||
def account_statuses(self, user_id=None, limit=None, max_id=None):
|
||||
# create 10 statuses
|
||||
# the first 2 will be checked in the first batch (in production it would be 40)
|
||||
user_toots = []
|
||||
def make_toot(i=1):
|
||||
if i < 11:
|
||||
keys = ("id", "created_at", "reblog", "tags", "visibility")
|
||||
vals = (
|
||||
i,
|
||||
datetime.datetime(2018, 11, i, 23, 15, 15, 718000, tzinfo=tzutc()),
|
||||
False,
|
||||
[],
|
||||
"public"
|
||||
)
|
||||
user_toot = dict(zip(keys, vals))
|
||||
user_toots.append(user_toot)
|
||||
total = i + 1
|
||||
make_toot(total)
|
||||
|
||||
user_toots.sort(reverse=True)
|
||||
make_toot(1) # make the batch of toots
|
||||
# ignore user_id
|
||||
# filter for toots with id smaller than max_id
|
||||
this_batch = []
|
||||
# use dict_to_obj to make a toot for each toot in the obj then a list from that
|
||||
this_batch = [ dict2obj(t) for t in user_toots if t["id"] > max_id][:limit]
|
||||
return this_batch
|
||||
|
||||
# mock argparse objects (options)
|
||||
class Namespace:
|
||||
def __init__(self, archive_deleted=False, datestamp=False, hide_skipped=False, retry_mins=1, schedule=False, test=False, time=False, quiet=False):
|
||||
self.archive_deleted = archive_deleted
|
||||
self.datestamp = datestamp
|
||||
self.schedule = schedule
|
||||
self.time = time
|
||||
self.test = test
|
||||
self.hide_skipped = hide_skipped
|
||||
self.quiet = quiet
|
||||
self.retry_mins = retry_mins
|
||||
|
||||
@pytest.fixture
|
||||
def mock_github_response(monkeypatch):
|
||||
|
||||
def mock_get(*args, **kwargs):
|
||||
return MockGitHub()
|
||||
|
||||
monkeypatch.setattr(requests, "get", mock_get)
|
||||
|
||||
########################
|
||||
# TESTS #
|
||||
########################
|
||||
|
||||
def test_archive_toot(tmpdir):
|
||||
p = tmpdir.mkdir("archive")
|
||||
config_file['archive'] = str(p) # make archive directory a temp test dir
|
||||
|
||||
ephemetoot.archive_toot(config_file, toot)
|
||||
|
||||
file_exists = os.path.exists(p + "/104136090490756999.json")
|
||||
assert file_exists
|
||||
|
||||
def test_check_batch(capfd, monkeypatch):
|
||||
config = config_file
|
||||
options = Namespace(archive_deleted=False)
|
||||
mastodon = Mocktodon()
|
||||
user_id = "test_user_id"
|
||||
# limit to 2 so check_batch calls itself for the last 8 toots
|
||||
timeline = mastodon.account_statuses(user_id=user_id, limit=2, max_id=0)
|
||||
# monkeypatch process_toot to add 1 to deleted_count and return
|
||||
# this simulates what would happen if the toot was being deleted
|
||||
monkeypatch.setattr(
|
||||
"ephemetoot.ephemetoot.process_toot",
|
||||
lambda config, options, mastodon, deleted_count, toot: deleted_count + 1
|
||||
)
|
||||
# run check_batch
|
||||
ephemetoot.check_batch(config, options, mastodon, user_id, timeline, 0)
|
||||
# deleted_count should be 10
|
||||
output = capfd.readouterr().out.split("\n")
|
||||
assert output[0] == "Removed 10 toots."
|
||||
|
||||
def test_console_print(capfd):
|
||||
ephemetoot.console_print("test123", Namespace(test=False, hide_skipped=False, quiet=False), False)
|
||||
assert capfd.readouterr().out == "test123\n"
|
||||
|
||||
def test_console_print_quiet():
|
||||
result = ephemetoot.console_print("test123", Namespace(test=False, hide_skipped=False, quiet=True), False)
|
||||
assert result == None
|
||||
|
||||
def test_console_print_skip():
|
||||
result = ephemetoot.console_print("test123", Namespace(test=False, hide_skipped=True, quiet=False), True)
|
||||
assert result == None
|
||||
|
||||
def test_datestamp_now():
|
||||
datestamp = ephemetoot.datestamp_now()
|
||||
date_object = datetime.datetime.strptime(datestamp, "%a %d %b %Y %H:%M:%S %z")
|
||||
# use timetuple() to exclude differences in milliseconds
|
||||
assert datetime.datetime.now(timezone.utc).timetuple() == date_object.timetuple()
|
||||
|
||||
def test_init(monkeypatch, tmpdir):
|
||||
|
||||
# monkeypatch current directory
|
||||
current_dir = tmpdir.mkdir("current_dir") # temporary directory for testing
|
||||
monkeypatch.chdir(current_dir)
|
||||
|
||||
# monkeypatch input ...outputs
|
||||
monkeypatch.setattr("ephemetoot.ephemetoot.compulsory_input", lambda a, b, c: "compulsory")
|
||||
monkeypatch.setattr("ephemetoot.ephemetoot.digit_input", lambda a, b, c: "14")
|
||||
monkeypatch.setattr("ephemetoot.ephemetoot.yes_no_input", lambda a, b: "false")
|
||||
monkeypatch.setattr("ephemetoot.ephemetoot.optional_input", lambda a, b, c: "optional")
|
||||
|
||||
# run init
|
||||
ephemetoot.init()
|
||||
assert os.path.exists( os.path.join(current_dir, "config.yaml"))
|
||||
|
||||
def test_jsondefault():
|
||||
d = ephemetoot.jsondefault(toot.created_at)
|
||||
assert d == "2020-05-09T02:17:18.598000+00:00"
|
||||
|
||||
def test_process_toot(capfd, tmpdir, monkeypatch):
|
||||
# config uses config_listed at top of this tests file
|
||||
p = tmpdir.mkdir("archive") # use temporary test directory
|
||||
config_file["archive"] = str(p)
|
||||
config_file["keep_pinned"] = False
|
||||
config_file["toots_to_keep"] = []
|
||||
config_file["visibility_to_keep"] = []
|
||||
options = Namespace(archive_deleted=False)
|
||||
mastodon = Mocktodon()
|
||||
toot_dict["pinned"] = False
|
||||
toot_dict["visibility"] = "public"
|
||||
toot_dict["reblog"] = False
|
||||
toot = dict2obj(toot_dict)
|
||||
ephemetoot.process_toot(config_file, options, mastodon, 0, toot)
|
||||
assert capfd.readouterr().out == "❌ deleting toot 104136090490756999 tooted 09 May 2020\n"
|
||||
|
||||
def test_process_toot_pinned(capfd, tmpdir):
|
||||
# config uses config_listed at top of this tests file
|
||||
p = tmpdir.mkdir("archive") # use temporary test directory
|
||||
config_file["archive"] = str(p)
|
||||
config_file["keep_pinned"] = True
|
||||
options = Namespace(archive_deleted=False)
|
||||
mastodon = Mocktodon()
|
||||
toot_dict["pinned"] = True
|
||||
toot = dict2obj(toot_dict)
|
||||
ephemetoot.process_toot(config_file, options, mastodon, 0, toot)
|
||||
assert capfd.readouterr().out == "📌 skipping pinned toot - 104136090490756999\n"
|
||||
def test_process_toot_saved(capfd, tmpdir):
|
||||
# config uses config_listed at top of this tests file
|
||||
p = tmpdir.mkdir("archive") # use temporary test directory
|
||||
config_file["archive"] = str(p)
|
||||
config_file["keep_pinned"] = False
|
||||
config_file["toots_to_keep"].append(104136090490756999)
|
||||
options = Namespace(archive_deleted=False)
|
||||
mastodon = Mocktodon()
|
||||
toot_dict["pinned"] = False
|
||||
toot = dict2obj(toot_dict)
|
||||
ephemetoot.process_toot(config_file, options, mastodon, 0, toot)
|
||||
assert capfd.readouterr().out == "💾 skipping saved toot - 104136090490756999\n"
|
||||
|
||||
def test_process_toot_visibility(capfd, tmpdir):
|
||||
# config uses config_listed at top of this tests file
|
||||
p = tmpdir.mkdir("archive") # use temporary test directory
|
||||
config_file["archive"] = str(p)
|
||||
config_file["keep_pinned"] = False # is true above so make false
|
||||
config_file["toots_to_keep"].remove(104136090490756999) # don't keep this toot
|
||||
config_file["visibility_to_keep"].append("testing")
|
||||
options = Namespace(archive_deleted=False)
|
||||
mastodon = Mocktodon()
|
||||
toot_dict["pinned"] = False # is true above so make false
|
||||
toot_dict["visibility"] = "testing"
|
||||
toot = dict2obj(toot_dict)
|
||||
ephemetoot.process_toot(config_file, options, mastodon, 0, toot)
|
||||
assert capfd.readouterr().out == "👀 skipping testing toot - 104136090490756999\n"
|
||||
|
||||
def test_process_toot_hashtag(capfd, tmpdir, monkeypatch):
|
||||
# config uses config_listed at top of this tests file
|
||||
p = tmpdir.mkdir("archive") # use temporary test directory
|
||||
config_file["archive"] = str(p)
|
||||
config_file["keep_pinned"] = False
|
||||
config_file["toots_to_keep"] = []
|
||||
config_file["visibility_to_keep"] = []
|
||||
options = Namespace(archive_deleted=False)
|
||||
mastodon = Mocktodon()
|
||||
toot_dict["pinned"] = False
|
||||
toot_dict["visibility"] = "public"
|
||||
toot_dict["reblog"] = True
|
||||
toot = dict2obj(toot_dict)
|
||||
|
||||
ephemetoot.process_toot(config_file, options, mastodon, 0, toot)
|
||||
assert capfd.readouterr().out == "👎 unboosting toot 104136090490756999 boosted 09 May 2020\n"
|
||||
|
||||
def test_retry_on_error():
|
||||
# Namespace object constructed from top of tests (representing options)
|
||||
# toot comes from variable at top of test
|
||||
mastodon = Mocktodon()
|
||||
toot = dict2obj(toot_dict)
|
||||
retry = ephemetoot.retry_on_error(Namespace(retry_mins=True), mastodon, toot, 5 )
|
||||
assert retry == None # should not return an error
|
||||
|
||||
def test_retry_on_error_max_tries():
|
||||
# Namespace object constructed from top of tests (representing options)
|
||||
# toot and mastodon come from objects at top of test
|
||||
with pytest.raises(TimeoutError):
|
||||
mastodon = Mocktodon()
|
||||
toot = dict2obj(toot_dict)
|
||||
retry = ephemetoot.retry_on_error(Namespace(retry_mins=True), mastodon, toot, 7 )
|
||||
|
||||
def test_schedule(monkeypatch, tmpdir):
|
||||
|
||||
home = tmpdir.mkdir("current_dir") # temporary directory for testing
|
||||
launch = tmpdir.mkdir("TestAgents") # temporary directory for testing
|
||||
|
||||
# monkeypatch directories and suppress the plist loading process
|
||||
# NOTE: it may be possible to test the plist loading process
|
||||
# but I can't work out how to do it universally / consistently
|
||||
|
||||
def mock_current_dir():
|
||||
return str(home)
|
||||
|
||||
def mock_home_dir_expansion(arg):
|
||||
return str(launch)
|
||||
|
||||
def suppress_subprocess(args, stdout=None, stderr=None, shell=None):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(os, "getcwd", mock_current_dir)
|
||||
monkeypatch.setattr(os.path, "expanduser", mock_home_dir_expansion)
|
||||
monkeypatch.setattr(subprocess, "run", suppress_subprocess)
|
||||
|
||||
# now we run the function we're testing
|
||||
ephemetoot.schedule(Namespace(schedule=".", time=None))
|
||||
|
||||
# assert the plist file was created
|
||||
plist_file = os.path.join(launch, "ephemetoot.scheduler.plist")
|
||||
assert os.path.lexists(plist_file)
|
||||
|
||||
# check that correct values were modified in the file
|
||||
f = open(plist_file, "r")
|
||||
plist = f.readlines()
|
||||
assert plist[7] == " <string>" + str(home) + "</string>\n"
|
||||
assert plist[7] == " <string>" + str(home) + "</string>\n"
|
||||
assert plist[10] == " <string>" + sys.argv[0] + "</string>\n"
|
||||
assert plist[12] == " <string>" + str(home) + "/config.yaml</string>\n"
|
||||
assert plist[15] == " <string>" + str(home) + "/ephemetoot.log</string>\n"
|
||||
assert plist[17] == " <string>" + str(home) + "/ephemetoot.error.log</string>\n"
|
||||
|
||||
def test_schedule_with_time(monkeypatch, tmpdir):
|
||||
|
||||
home = tmpdir.mkdir("current_dir") # temporary directory for testing
|
||||
launch = tmpdir.mkdir("TestAgents") # temporary directory for testing
|
||||
|
||||
# monkeypatch directories and suppress the plist loading process
|
||||
# NOTE: it may be possible to test the plist loading process
|
||||
# but I can't work out how to do it universally / consistently
|
||||
|
||||
def mock_current_dir():
|
||||
return str(home)
|
||||
|
||||
def mock_home_dir_expansion(arg):
|
||||
return str(launch)
|
||||
|
||||
def suppress_subprocess(args, stdout=None, stderr=None, shell=None):
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(os, "getcwd", mock_current_dir)
|
||||
monkeypatch.setattr(os.path, "expanduser", mock_home_dir_expansion)
|
||||
monkeypatch.setattr(subprocess, "run", suppress_subprocess)
|
||||
|
||||
# now we run the function we're testing
|
||||
ephemetoot.schedule(Namespace(schedule=".", time=["10", "30"]))
|
||||
|
||||
# assert the plist file was created
|
||||
plist_file = os.path.join(launch, "ephemetoot.scheduler.plist")
|
||||
assert os.path.lexists(plist_file)
|
||||
|
||||
# assert that correct values were modified in the file
|
||||
f = open(plist_file, "r")
|
||||
plist = f.readlines()
|
||||
|
||||
assert plist[21] == " <integer>10</integer>\n"
|
||||
assert plist[23] == " <integer>30</integer>\n"
|
||||
|
||||
def test_tooted_date():
|
||||
string = ephemetoot.tooted_date(toot)
|
||||
created = datetime.datetime(2020, 5, 9, 2, 17, 18, 598000, tzinfo=timezone.utc)
|
||||
test_string = created.strftime("%d %b %Y")
|
||||
|
||||
assert string == test_string
|
||||
|
||||
def test_version(mock_github_response, capfd):
|
||||
ephemetoot.version("TEST_VERSION")
|
||||
output = capfd.readouterr().out
|
||||
msg = """
|
||||
ephemetoot ==> 🥳 ==> 🧼 ==> 😇
|
||||
-------------------------------
|
||||
You are using release: \033[92mvTEST_VERSION\033[0m
|
||||
The latest release is: \033[92mvLATEST_VERSION\033[0m
|
||||
To upgrade to the most recent version run \033[92mpip3 install --update ephemetoot\033[0m\n"""
|
||||
|
||||
assert output == msg
|
Loading…
Reference in New Issue