__filename__ = "tests.py" __author__ = "Bob Mottram" __license__ = "AGPL3+" __version__ = "1.2.0" __maintainer__ = "Bob Mottram" __email__ = "bob@libreserver.org" __status__ = "Production" __module_group__ = "Testing" import base64 from cryptography.hazmat.primitives import hashes from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.serialization import load_pem_private_key from cryptography.hazmat.primitives.serialization import load_pem_public_key from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.asymmetric import utils as hazutils import time import os import shutil import json import datetime from shutil import copyfile from random import randint from time import gmtime, strftime from pprint import pprint from httpsig import get_digest_algorithm_from_headers from httpsig import get_digest_prefix from httpsig import create_signed_header from httpsig import sign_post_headers from httpsig import sign_post_headers_new from httpsig import verify_post_headers from httpsig import message_content_digest from cache import store_person_in_cache from cache import get_person_from_cache from threads import thread_with_trace from daemon import run_daemon from session import create_session from session import get_json from posts import get_actor_from_in_reply_to from posts import regenerate_index_for_box from posts import remove_post_interactions from posts import get_mentioned_people from posts import valid_content_warning from posts import delete_all_posts from posts import create_public_post from posts import send_post from posts import no_of_followers_on_domain from posts import group_followers_by_domain from posts import archive_posts_for_person from posts import send_post_via_server from posts import seconds_between_published from follow import clear_follows from follow import clear_followers from follow import send_follow_requestViaServer from follow import send_unfollow_request_via_server from siteactive import site_is_active from utils import convert_to_snake_case from utils import get_sha_256 from utils import dangerous_svg from utils import can_reply_to from utils import is_group_account from utils import get_actor_languages_list from utils import get_category_types from utils import get_supported_languages from utils import set_config_param from utils import is_group_actor from utils import date_string_to_seconds from utils import date_seconds_to_string from utils import valid_password from utils import user_agent_domain from utils import camel_case_split from utils import decoded_host from utils import get_full_domain from utils import valid_nickname from utils import first_paragraph_from_string from utils import remove_id_ending from utils import update_recent_posts_cache from utils import follow_person from utils import get_nickname_from_actor from utils import get_domain_from_actor from utils import copytree from utils import load_json from utils import save_json from utils import get_status_number from utils import get_followers_of_person from utils import remove_html from utils import dangerous_markup from utils import acct_dir from pgp import extract_pgp_public_key from pgp import pgp_public_key_upload from utils import contains_pgp_public_key from follow import add_follower_of_person from follow import unfollow_account from follow import unfollower_of_account from follow import send_follow_request from person import create_person from person import create_group from person import set_display_nickname from person import set_bio # from person import generate_rsa_key from skills import set_skill_level from skills import actor_skill_value from skills import set_skills_from_dict from skills import actor_has_skill from roles import set_rolesFromList from roles import set_role from roles import actor_has_role from auth import constant_time_string_check from auth import create_basic_auth_header from auth import authorize_basic from auth import store_basic_credentials from like import like_post from like import send_like_via_server from reaction import reaction_post from reaction import send_reaction_via_server from reaction import valid_emoji_content from announce import announce_public from announce import send_announce_via_server from city import parse_nogo_string from city import spoof_geolocation from city import point_in_nogo from media import get_image_dimensions from media import get_media_path from media import get_attachment_media_type from delete import send_delete_via_server from inbox import json_post_allows_comments from inbox import valid_inbox from inbox import valid_inbox_filenames from categories import guess_hashtag_category from content import words_similarity from content import get_price_from_string from content import limit_repeated_words from content import switch_words from content import extract_text_fields_in_post from content import valid_hash_tag from content import html_replace_email_quote from content import html_replace_quote_marks from content import dangerous_css from content import add_web_links from content import replace_emoji_from_tags from content import add_html_tags from content import remove_long_words from content import replace_content_duplicates from content import remove_text_formatting from content import remove_html_tag from theme import update_default_themes_list from theme import set_cs_sparam from theme import scan_themes_for_scripts from linked_data_sig import generate_json_signature from linked_data_sig import verify_json_signature from newsdaemon import hashtag_rule_tree from newsdaemon import hashtag_rule_resolve from newswire import get_newswire_tags from newswire import parse_feed_date from newswire import limit_word_lengths from mastoapiv1 import get_masto_api_v1id_from_nickname from mastoapiv1 import get_nickname_from_masto_api_v1id from webapp_post import prepare_html_post_nickname from speaker import speaker_replace_links from markdown import markdown_to_html from languages import set_actor_languages from languages import get_actor_languages from languages import get_links_from_content from languages import add_links_to_content from languages import libretranslate from languages import libretranslate_languages from shares import authorize_shared_items from shares import generate_shared_item_federation_tokens from shares import create_shared_item_federation_token from shares import update_shared_item_federation_token from shares import merge_shared_item_tokens from shares import send_share_via_server from shares import get_shared_items_catalog_via_server from blocking import load_cw_lists from blocking import add_cw_from_lists testServerGroupRunning = False testServerAliceRunning = False testServerBobRunning = False testServerEveRunning = False thrGroup = None thrAlice = None thrBob = None thrEve = None def _test_http_signed_get(base_dir: str): print('testHttpSignedGET') http_prefix = 'https' debug = True boxpath = "/users/Actor" host = "epicyon.libreserver.org" content_length = "0" user_agent = "http.rb/4.4.1 (Mastodon/3.4.1; +https://octodon.social/)" dateStr = 'Wed, 01 Sep 2021 16:11:10 GMT' accept_encoding = 'gzip' accept = \ 'application/activity+json, application/ld+json' signature = \ 'keyId="https://octodon.social/actor#main-key",' + \ 'algorithm="rsa-sha256",' + \ 'headers="(request-target) host date accept",' + \ 'signature="Fe53PS9A2OSP4x+W/svhA' + \ 'jUKHBvnAR73Ez+H32au7DQklLk08Lvm8al' + \ 'LS7pCor28yfyx+DfZADgq6G1mLLRZo0OOn' + \ 'PFSog7DhdcygLhBUMS0KlT5KVGwUS0tw' + \ 'jdiHv4OC83RiCr/ZySBgOv65YLHYmGCi5B' + \ 'IqSZJRkqi8+SLmLGESlNOEzKu+jIxOBY' + \ 'mEEdIpNrDeE5YrFKpfTC3vS2GnxGOo5J/4' + \ 'lB2h+dlUpso+sv5rDz1d1FsqRWK8waV7' + \ '4HUfLV+qbgYRceOTyZIi50vVqLvt9CTQes' + \ 'KZHG3GrrPfaBuvoUbR4MCM3BUvpB7EzL' + \ '9F17Y+Ea9mo8zjqzZm8HaZQ=="' publicKeyPem = \ '-----BEGIN PUBLIC KEY-----\n' + \ 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMII' + \ 'BCgKCAQEA1XT+ov/i4LDYuaXCwh4r\n' + \ '2rVfWtnz68wnFx3knwymwtRoAc/SFGzp9ye' + \ '5ogG1uPcbe7MeirZHhaBICynPlL32\n' + \ 's9OYootI7MsQWn+vu7azxiXO7qcTPByvGcl' + \ '0vpLhtT/ApmlMintkRTVXdzBdJVM0\n' + \ 'UsmYKg6U+IHNL+a1gURHGXep2Ih0BJMh4Aa' + \ 'DbaID6jtpJZvbIkYgJ4IJucOe+A3T\n' + \ 'YPMwkBA84ew+hso+vKQfTunyDInuPQbEzrA' + \ 'zMJXEHS7IpBhdS4/cEox86BoDJ/q0\n' + \ 'KOEOUpUDniFYWb9k1+9B387OviRDLIcLxNZ' + \ 'nf+bNq8d+CwEXY2xGsToBle/q74d8\n' + \ 'BwIDAQAB\n' + \ '-----END PUBLIC KEY-----\n' headers = { "user-agent": user_agent, "content-length": content_length, "host": host, "date": dateStr, "accept": accept, "accept-encoding": accept_encoding, "signature": signature } GETmethod = True messageBodyDigest = None messageBodyJsonStr = '' noRecencyCheck = True assert verify_post_headers(http_prefix, publicKeyPem, headers, boxpath, GETmethod, messageBodyDigest, messageBodyJsonStr, debug, noRecencyCheck) # Change a single character and the signature should fail headers['date'] = headers['date'].replace(':10', ':11') assert not verify_post_headers(http_prefix, publicKeyPem, headers, boxpath, GETmethod, messageBodyDigest, messageBodyJsonStr, debug, noRecencyCheck) path = base_dir + '/.testHttpsigGET' if os.path.isdir(path): shutil.rmtree(path, ignore_errors=False, onerror=None) os.mkdir(path) os.chdir(path) nickname = 'testactor' hostDomain = 'someother.instance' domain = 'argumentative.social' http_prefix = 'https' port = 443 withDigest = False password = 'SuperSecretPassword' noRecencyCheck = True privateKeyPem, publicKeyPem, person, wfEndpoint = \ create_person(path, nickname, domain, port, http_prefix, False, False, password) assert privateKeyPem assert publicKeyPem messageBodyJsonStr = '' headersDomain = get_full_domain(hostDomain, port) dateStr = 'Tue, 14 Sep 2021 16:19:00 GMT' boxpath = '/inbox' accept = 'application/json' # accept = 'application/activity+json' headers = { 'user-agent': 'Epicyon/1.2.0; +https://' + domain + '/', 'host': headersDomain, 'date': dateStr, 'accept': accept, 'content-length': 0 } signatureHeader = create_signed_header(dateStr, privateKeyPem, nickname, domain, port, hostDomain, port, boxpath, http_prefix, False, None, accept) headers['signature'] = signatureHeader['signature'] GETmethod = not withDigest assert verify_post_headers(http_prefix, publicKeyPem, headers, boxpath, GETmethod, None, messageBodyJsonStr, debug, noRecencyCheck) if os.path.isdir(path): shutil.rmtree(path, ignore_errors=False, onerror=None) def _test_sign_and_verify() -> None: print('testSignAndVerify') publicKeyPem = \ '-----BEGIN RSA PUBLIC KEY-----\n' + \ 'MIIBCgKCAQEAhAKYdtoeoy8zcAcR874L8' + \ 'cnZxKzAGwd7v36APp7Pv6Q2jdsPBRrw\n' + \ 'WEBnez6d0UDKDwGbc6nxfEXAy5mbhgajz' + \ 'rw3MOEt8uA5txSKobBpKDeBLOsdJKFq\n' + \ 'MGmXCQvEG7YemcxDTRPxAleIAgYYRjTSd' + \ '/QBwVW9OwNFhekro3RtlinV0a75jfZg\n' + \ 'kne/YiktSvLG34lw2zqXBDTC5NHROUqGT' + \ 'lML4PlNZS5Ri2U4aCNx2rUPRcKIlE0P\n' + \ 'uKxI4T+HIaFpv8+rdV6eUgOrB2xeI1dSF' + \ 'Fn/nnv5OoZJEIB+VmuKn3DCUcCZSFlQ\n' + \ 'PSXSfBDiUGhwOw76WuSSsf1D4b/vLoJ10wIDAQAB\n' + \ '-----END RSA PUBLIC KEY-----\n' privateKeyPem = \ '-----BEGIN RSA PRIVATE KEY-----\n' + \ 'MIIEqAIBAAKCAQEAhAKYdtoeoy8zcAcR8' + \ '74L8cnZxKzAGwd7v36APp7Pv6Q2jdsP\n' + \ 'BRrwWEBnez6d0UDKDwGbc6nxfEXAy5mbh' + \ 'gajzrw3MOEt8uA5txSKobBpKDeBLOsd\n' + \ 'JKFqMGmXCQvEG7YemcxDTRPxAleIAgYYR' + \ 'jTSd/QBwVW9OwNFhekro3RtlinV0a75\n' + \ 'jfZgkne/YiktSvLG34lw2zqXBDTC5NHRO' + \ 'UqGTlML4PlNZS5Ri2U4aCNx2rUPRcKI\n' + \ 'lE0PuKxI4T+HIaFpv8+rdV6eUgOrB2xeI' + \ '1dSFFn/nnv5OoZJEIB+VmuKn3DCUcCZ\n' + \ 'SFlQPSXSfBDiUGhwOw76WuSSsf1D4b/vL' + \ 'oJ10wIDAQABAoIBAG/JZuSWdoVHbi56\n' + \ 'vjgCgkjg3lkO1KrO3nrdm6nrgA9P9qaPj' + \ 'xuKoWaKO1cBQlE1pSWp/cKncYgD5WxE\n' + \ 'CpAnRUXG2pG4zdkzCYzAh1i+c34L6oZoH' + \ 'sirK6oNcEnHveydfzJL5934egm6p8DW\n' + \ '+m1RQ70yUt4uRc0YSor+q1LGJvGQHReF0' + \ 'WmJBZHrhz5e63Pq7lE0gIwuBqL8SMaA\n' + \ 'yRXtK+JGxZpImTq+NHvEWWCu09SCq0r83' + \ '8ceQI55SvzmTkwqtC+8AT2zFviMZkKR\n' + \ 'Qo6SPsrqItxZWRty2izawTF0Bf5S2VAx7' + \ 'O+6t3wBsQ1sLptoSgX3QblELY5asI0J\n' + \ 'YFz7LJECgYkAsqeUJmqXE3LP8tYoIjMIA' + \ 'KiTm9o6psPlc8CrLI9CH0UbuaA2JCOM\n' + \ 'cCNq8SyYbTqgnWlB9ZfcAm/cFpA8tYci9' + \ 'm5vYK8HNxQr+8FS3Qo8N9RJ8d0U5Csw\n' + \ 'DzMYfRghAfUGwmlWj5hp1pQzAuhwbOXFt' + \ 'xKHVsMPhz1IBtF9Y8jvgqgYHLbmyiu1\n' + \ 'mwJ5AL0pYF0G7x81prlARURwHo0Yf52kE' + \ 'w1dxpx+JXER7hQRWQki5/NsUEtv+8RT\n' + \ 'qn2m6qte5DXLyn83b1qRscSdnCCwKtKWU' + \ 'ug5q2ZbwVOCJCtmRwmnP131lWRYfj67\n' + \ 'B/xJ1ZA6X3GEf4sNReNAtaucPEelgR2ns' + \ 'N0gKQKBiGoqHWbK1qYvBxX2X3kbPDkv\n' + \ '9C+celgZd2PW7aGYLCHq7nPbmfDV0yHcW' + \ 'jOhXZ8jRMjmANVR/eLQ2EfsRLdW69bn\n' + \ 'f3ZD7JS1fwGnO3exGmHO3HZG+6AvberKY' + \ 'VYNHahNFEw5TsAcQWDLRpkGybBcxqZo\n' + \ '81YCqlqidwfeO5YtlO7etx1xLyqa2NsCe' + \ 'G9A86UjG+aeNnXEIDk1PDK+EuiThIUa\n' + \ '/2IxKzJKWl1BKr2d4xAfR0ZnEYuRrbeDQ' + \ 'YgTImOlfW6/GuYIxKYgEKCFHFqJATAG\n' + \ 'IxHrq1PDOiSwXd2GmVVYyEmhZnbcp8Cxa' + \ 'EMQoevxAta0ssMK3w6UsDtvUvYvF22m\n' + \ 'qQKBiD5GwESzsFPy3Ga0MvZpn3D6EJQLg' + \ 'snrtUPZx+z2Ep2x0xc5orneB5fGyF1P\n' + \ 'WtP+fG5Q6Dpdz3LRfm+KwBCWFKQjg7uTx' + \ 'cjerhBWEYPmEMKYwTJF5PBG9/ddvHLQ\n' + \ 'EQeNC8fHGg4UXU8mhHnSBt3EA10qQJfRD' + \ 's15M38eG2cYwB1PZpDHScDnDA0=\n' + \ '-----END RSA PRIVATE KEY-----' # sign signedHeaderText = \ '(request-target): get /actor\n' + \ 'host: octodon.social\n' + \ 'date: Tue, 14 Sep 2021 16:19:00 GMT\n' + \ 'accept: application/json' headerDigest = get_sha_256(signedHeaderText.encode('ascii')) key = load_pem_private_key(privateKeyPem.encode('utf-8'), None, backend=default_backend()) rawSignature = key.sign(headerDigest, padding.PKCS1v15(), hazutils.Prehashed(hashes.SHA256())) signature1 = base64.b64encode(rawSignature).decode('ascii') # verify paddingStr = padding.PKCS1v15() alg = hazutils.Prehashed(hashes.SHA256()) pubkey = load_pem_public_key(publicKeyPem.encode('utf-8'), backend=default_backend()) signature2 = base64.b64decode(signature1) pubkey.verify(signature2, headerDigest, paddingStr, alg) def _test_http_sig_new(algorithm: str, digestAlgorithm: str): print('testHttpSigNew') http_prefix = 'https' port = 443 debug = True messageBodyJson = {"hello": "world"} messageBodyJsonStr = json.dumps(messageBodyJson) nickname = 'foo' pathStr = "/" + nickname + "?param=value&pet=dog HTTP/1.1" domain = 'example.com' dateStr = 'Tue, 20 Apr 2021 02:07:55 GMT' digestPrefix = get_digest_prefix(digestAlgorithm) digestStr = digestPrefix + '=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=' bodyDigest = message_content_digest(messageBodyJsonStr, digestAlgorithm) assert bodyDigest in digestStr contentLength = 18 content_type = 'application/activity+json' publicKeyPem = \ '-----BEGIN RSA PUBLIC KEY-----\n' + \ 'MIIBCgKCAQEAhAKYdtoeoy8zcAcR874L8' + \ 'cnZxKzAGwd7v36APp7Pv6Q2jdsPBRrw\n' + \ 'WEBnez6d0UDKDwGbc6nxfEXAy5mbhgajz' + \ 'rw3MOEt8uA5txSKobBpKDeBLOsdJKFq\n' + \ 'MGmXCQvEG7YemcxDTRPxAleIAgYYRjTSd' + \ '/QBwVW9OwNFhekro3RtlinV0a75jfZg\n' + \ 'kne/YiktSvLG34lw2zqXBDTC5NHROUqGT' + \ 'lML4PlNZS5Ri2U4aCNx2rUPRcKIlE0P\n' + \ 'uKxI4T+HIaFpv8+rdV6eUgOrB2xeI1dSF' + \ 'Fn/nnv5OoZJEIB+VmuKn3DCUcCZSFlQ\n' + \ 'PSXSfBDiUGhwOw76WuSSsf1D4b/vLoJ10wIDAQAB\n' + \ '-----END RSA PUBLIC KEY-----\n' privateKeyPem = \ '-----BEGIN RSA PRIVATE KEY-----\n' + \ 'MIIEqAIBAAKCAQEAhAKYdtoeoy8zcAcR8' + \ '74L8cnZxKzAGwd7v36APp7Pv6Q2jdsP\n' + \ 'BRrwWEBnez6d0UDKDwGbc6nxfEXAy5mbh' + \ 'gajzrw3MOEt8uA5txSKobBpKDeBLOsd\n' + \ 'JKFqMGmXCQvEG7YemcxDTRPxAleIAgYYR' + \ 'jTSd/QBwVW9OwNFhekro3RtlinV0a75\n' + \ 'jfZgkne/YiktSvLG34lw2zqXBDTC5NHRO' + \ 'UqGTlML4PlNZS5Ri2U4aCNx2rUPRcKI\n' + \ 'lE0PuKxI4T+HIaFpv8+rdV6eUgOrB2xeI' + \ '1dSFFn/nnv5OoZJEIB+VmuKn3DCUcCZ\n' + \ 'SFlQPSXSfBDiUGhwOw76WuSSsf1D4b/vL' + \ 'oJ10wIDAQABAoIBAG/JZuSWdoVHbi56\n' + \ 'vjgCgkjg3lkO1KrO3nrdm6nrgA9P9qaPj' + \ 'xuKoWaKO1cBQlE1pSWp/cKncYgD5WxE\n' + \ 'CpAnRUXG2pG4zdkzCYzAh1i+c34L6oZoH' + \ 'sirK6oNcEnHveydfzJL5934egm6p8DW\n' + \ '+m1RQ70yUt4uRc0YSor+q1LGJvGQHReF0' + \ 'WmJBZHrhz5e63Pq7lE0gIwuBqL8SMaA\n' + \ 'yRXtK+JGxZpImTq+NHvEWWCu09SCq0r83' + \ '8ceQI55SvzmTkwqtC+8AT2zFviMZkKR\n' + \ 'Qo6SPsrqItxZWRty2izawTF0Bf5S2VAx7' + \ 'O+6t3wBsQ1sLptoSgX3QblELY5asI0J\n' + \ 'YFz7LJECgYkAsqeUJmqXE3LP8tYoIjMIA' + \ 'KiTm9o6psPlc8CrLI9CH0UbuaA2JCOM\n' + \ 'cCNq8SyYbTqgnWlB9ZfcAm/cFpA8tYci9' + \ 'm5vYK8HNxQr+8FS3Qo8N9RJ8d0U5Csw\n' + \ 'DzMYfRghAfUGwmlWj5hp1pQzAuhwbOXFt' + \ 'xKHVsMPhz1IBtF9Y8jvgqgYHLbmyiu1\n' + \ 'mwJ5AL0pYF0G7x81prlARURwHo0Yf52kE' + \ 'w1dxpx+JXER7hQRWQki5/NsUEtv+8RT\n' + \ 'qn2m6qte5DXLyn83b1qRscSdnCCwKtKWU' + \ 'ug5q2ZbwVOCJCtmRwmnP131lWRYfj67\n' + \ 'B/xJ1ZA6X3GEf4sNReNAtaucPEelgR2ns' + \ 'N0gKQKBiGoqHWbK1qYvBxX2X3kbPDkv\n' + \ '9C+celgZd2PW7aGYLCHq7nPbmfDV0yHcW' + \ 'jOhXZ8jRMjmANVR/eLQ2EfsRLdW69bn\n' + \ 'f3ZD7JS1fwGnO3exGmHO3HZG+6AvberKY' + \ 'VYNHahNFEw5TsAcQWDLRpkGybBcxqZo\n' + \ '81YCqlqidwfeO5YtlO7etx1xLyqa2NsCe' + \ 'G9A86UjG+aeNnXEIDk1PDK+EuiThIUa\n' + \ '/2IxKzJKWl1BKr2d4xAfR0ZnEYuRrbeDQ' + \ 'YgTImOlfW6/GuYIxKYgEKCFHFqJATAG\n' + \ 'IxHrq1PDOiSwXd2GmVVYyEmhZnbcp8Cxa' + \ 'EMQoevxAta0ssMK3w6UsDtvUvYvF22m\n' + \ 'qQKBiD5GwESzsFPy3Ga0MvZpn3D6EJQLg' + \ 'snrtUPZx+z2Ep2x0xc5orneB5fGyF1P\n' + \ 'WtP+fG5Q6Dpdz3LRfm+KwBCWFKQjg7uTx' + \ 'cjerhBWEYPmEMKYwTJF5PBG9/ddvHLQ\n' + \ 'EQeNC8fHGg4UXU8mhHnSBt3EA10qQJfRD' + \ 's15M38eG2cYwB1PZpDHScDnDA0=\n' + \ '-----END RSA PRIVATE KEY-----' headers = { "host": domain, "date": dateStr, "digest": f'{digestPrefix}={bodyDigest}', "content-type": content_type, "content-length": str(contentLength) } signatureIndexHeader, signatureHeader = \ sign_post_headers_new(dateStr, privateKeyPem, nickname, domain, port, domain, port, pathStr, http_prefix, messageBodyJsonStr, algorithm, digestAlgorithm, debug) print('signatureIndexHeader1: ' + str(signatureIndexHeader)) print('signatureHeader1: ' + str(signatureHeader)) sigInput = "keyId=\"https://example.com/users/foo#main-key\"; " + \ "alg=hs2019; created=1618884475; " + \ "sig1=(@request-target, @created, host, date, digest, " + \ "content-type, content-length)" assert signatureIndexHeader == sigInput sig = "sig1=:NXAQ7AtDMR2iwhmH1qCwiZw5PVTjOw5+5kSu0Tsx/3gqz0D" + \ "py7OQbWqFHrNB7MmS4TukX/vDyQOFdElY5yxnEhbgRwKACq0AP4QH9H" + \ "CiRyCE8UXDdAkY4VUd6jrWjRHKRoqQN7I+Q5tb2Fu5cDfifw/PQc86Z" + \ "NmMhPrg3OjUJ9Q2Gj29NhgJ+4el1ECg0cAy4yG1M9AQ3KvQooQFvlg1" + \ "vp0H2xfbJQjv8FsR/lKiRdaVHqGR2CKrvxvPRPaOsFANp2wzEtiMk3O" + \ "TrBTYU+Zb53mIspfEeLxsNtcGmBDmQKZ9Pud8f99XGJrP+uDd3zKtnr" + \ "f3fUnRRqy37yhB7WVwkg==:" assert signatureHeader == sig debug = True headers['path'] = pathStr headers['signature'] = sig headers['signature-input'] = sigInput assert verify_post_headers(http_prefix, publicKeyPem, headers, pathStr, False, None, messageBodyJsonStr, debug, True) # make a deliberate mistake debug = False headers['signature'] = headers['signature'].replace('V', 'B') assert not verify_post_headers(http_prefix, publicKeyPem, headers, pathStr, False, None, messageBodyJsonStr, debug, True) def _test_httpsig_base(withDigest: bool, base_dir: str): print('testHttpsig(' + str(withDigest) + ')') path = base_dir + '/.testHttpsigBase' if os.path.isdir(path): shutil.rmtree(path, ignore_errors=False, onerror=None) os.mkdir(path) os.chdir(path) algorithm = 'rsa-sha256' digestAlgorithm = 'rsa-sha256' content_type = 'application/activity+json' nickname = 'socrates' hostDomain = 'someother.instance' domain = 'argumentative.social' http_prefix = 'https' port = 5576 password = 'SuperSecretPassword' privateKeyPem, publicKeyPem, person, wfEndpoint = \ create_person(path, nickname, domain, port, http_prefix, False, False, password) assert privateKeyPem if withDigest: messageBodyJson = { "a key": "a value", "another key": "A string", "yet another key": "Another string" } messageBodyJsonStr = json.dumps(messageBodyJson) else: messageBodyJsonStr = '' headersDomain = get_full_domain(hostDomain, port) dateStr = strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime()) boxpath = '/inbox' if not withDigest: headers = { 'host': headersDomain, 'date': dateStr, 'accept': content_type } signatureHeader = \ sign_post_headers(dateStr, privateKeyPem, nickname, domain, port, hostDomain, port, boxpath, http_prefix, None, content_type, algorithm, None) else: digestPrefix = get_digest_prefix(digestAlgorithm) bodyDigest = \ message_content_digest(messageBodyJsonStr, digestAlgorithm) contentLength = len(messageBodyJsonStr) headers = { 'host': headersDomain, 'date': dateStr, 'digest': f'{digestPrefix}={bodyDigest}', 'content-type': content_type, 'content-length': str(contentLength) } assert get_digest_algorithm_from_headers(headers) == digestAlgorithm signatureHeader = \ sign_post_headers(dateStr, privateKeyPem, nickname, domain, port, hostDomain, port, boxpath, http_prefix, messageBodyJsonStr, content_type, algorithm, digestAlgorithm) headers['signature'] = signatureHeader GETmethod = not withDigest debug = True assert verify_post_headers(http_prefix, publicKeyPem, headers, boxpath, GETmethod, None, messageBodyJsonStr, debug) if withDigest: # everything correct except for content-length headers['content-length'] = str(contentLength + 2) assert verify_post_headers(http_prefix, publicKeyPem, headers, boxpath, GETmethod, None, messageBodyJsonStr, False) is False assert verify_post_headers(http_prefix, publicKeyPem, headers, '/parambulator' + boxpath, GETmethod, None, messageBodyJsonStr, False) is False assert verify_post_headers(http_prefix, publicKeyPem, headers, boxpath, not GETmethod, None, messageBodyJsonStr, False) is False if not withDigest: # fake domain headers = { 'host': 'bogon.domain', 'date': dateStr, 'content-type': content_type } else: # correct domain but fake message messageBodyJsonStr = \ '{"a key": "a value", "another key": "Fake GNUs", ' + \ '"yet another key": "More Fake GNUs"}' contentLength = len(messageBodyJsonStr) digestPrefix = get_digest_prefix(digestAlgorithm) bodyDigest = \ message_content_digest(messageBodyJsonStr, digestAlgorithm) headers = { 'host': domain, 'date': dateStr, 'digest': f'{digestPrefix}={bodyDigest}', 'content-type': content_type, 'content-length': str(contentLength) } assert get_digest_algorithm_from_headers(headers) == digestAlgorithm headers['signature'] = signatureHeader assert verify_post_headers(http_prefix, publicKeyPem, headers, boxpath, not GETmethod, None, messageBodyJsonStr, False) is False os.chdir(base_dir) shutil.rmtree(path, ignore_errors=False, onerror=None) def _test_httpsig(base_dir: str): _test_httpsig_base(True, base_dir) _test_httpsig_base(False, base_dir) def _test_cache(): print('testCache') personUrl = "cat@cardboard.box" personJson = { "id": 123456, "test": "This is a test" } person_cache = {} store_person_in_cache(None, personUrl, personJson, person_cache, True) result = get_person_from_cache(None, personUrl, person_cache, True) assert result['id'] == 123456 assert result['test'] == 'This is a test' def _test_threads_function(param: str): for i in range(10000): time.sleep(2) def _test_threads(): print('testThreads') thr = \ thread_with_trace(target=_test_threads_function, args=('test',), daemon=True) thr.start() assert thr.is_alive() is True time.sleep(1) thr.kill() thr.join() assert thr.is_alive() is False def create_server_alice(path: str, domain: str, port: int, bobAddress: str, federation_list: [], hasFollows: bool, hasPosts: bool, send_threads: []): print('Creating test server: Alice on port ' + str(port)) if os.path.isdir(path): shutil.rmtree(path, ignore_errors=False, onerror=None) os.mkdir(path) os.chdir(path) shared_items_federated_domains = [] system_language = 'en' nickname = 'alice' http_prefix = 'http' proxy_type = None password = 'alicepass' max_replies = 64 domain_max_posts_per_day = 1000 account_max_posts_per_day = 1000 allow_deletion = True low_bandwidth = True privateKeyPem, publicKeyPem, person, wfEndpoint = \ create_person(path, nickname, domain, port, http_prefix, True, False, password) delete_all_posts(path, nickname, domain, 'inbox') delete_all_posts(path, nickname, domain, 'outbox') assert set_skill_level(path, nickname, domain, 'hacking', 90) assert set_role(path, nickname, domain, 'guru') if hasFollows: follow_person(path, nickname, domain, 'bob', bobAddress, federation_list, False, False) add_follower_of_person(path, nickname, domain, 'bob', bobAddress, federation_list, False, False) if hasPosts: testFollowersOnly = False testSaveToFile = True client_to_server = False testCommentsEnabled = True testAttachImageFilename = None testMediaType = None testImageDescription = None testCity = 'London, England' testInReplyTo = None testInReplyToAtomUri = None testSubject = None testSchedulePost = False testEventDate = None testEventTime = None testLocation = None testIsArticle = False conversationId = None content_license_url = 'https://creativecommons.org/licenses/by/4.0' create_public_post(path, nickname, domain, port, http_prefix, "No wise fish would go anywhere without a porpoise", testFollowersOnly, testSaveToFile, client_to_server, testCommentsEnabled, testAttachImageFilename, testMediaType, testImageDescription, testCity, testInReplyTo, testInReplyToAtomUri, testSubject, testSchedulePost, testEventDate, testEventTime, testLocation, testIsArticle, system_language, conversationId, low_bandwidth, content_license_url) create_public_post(path, nickname, domain, port, http_prefix, "Curiouser and curiouser!", testFollowersOnly, testSaveToFile, client_to_server, testCommentsEnabled, testAttachImageFilename, testMediaType, testImageDescription, testCity, testInReplyTo, testInReplyToAtomUri, testSubject, testSchedulePost, testEventDate, testEventTime, testLocation, testIsArticle, system_language, conversationId, low_bandwidth, content_license_url) create_public_post(path, nickname, domain, port, http_prefix, "In the gardens of memory, in the palace " + "of dreams, that is where you and I shall meet", testFollowersOnly, testSaveToFile, client_to_server, testCommentsEnabled, testAttachImageFilename, testMediaType, testImageDescription, testCity, testInReplyTo, testInReplyToAtomUri, testSubject, testSchedulePost, testEventDate, testEventTime, testLocation, testIsArticle, system_language, conversationId, low_bandwidth, content_license_url) regenerate_index_for_box(path, nickname, domain, 'outbox') global testServerAliceRunning testServerAliceRunning = True max_mentions = 10 max_emoji = 10 onion_domain = None i2p_domain = None allow_local_network_access = True max_newswire_posts = 20 dormant_months = 3 send_threads_timeout_mins = 30 max_followers = 10 verify_all_signatures = True broch_mode = False show_node_info_accounts = True show_node_info_version = True city = 'London, England' log_login_failures = False user_agents_blocked = [] max_like_count = 10 default_reply_interval_hrs = 9999999999 lists_enabled = '' content_license_url = 'https://creativecommons.org/licenses/by/4.0' print('Server running: Alice') run_daemon(content_license_url, lists_enabled, default_reply_interval_hrs, low_bandwidth, max_like_count, shared_items_federated_domains, user_agents_blocked, log_login_failures, city, show_node_info_accounts, show_node_info_version, broch_mode, verify_all_signatures, send_threads_timeout_mins, dormant_months, max_newswire_posts, allow_local_network_access, 2048, False, True, False, False, True, max_followers, 0, 100, 1024, 5, False, 0, False, 1, False, False, False, 5, True, True, 'en', __version__, "instance_id", False, path, domain, onion_domain, i2p_domain, None, None, port, port, http_prefix, federation_list, max_mentions, max_emoji, False, proxy_type, max_replies, domain_max_posts_per_day, account_max_posts_per_day, allow_deletion, True, True, False, send_threads, False) def create_server_bob(path: str, domain: str, port: int, aliceAddress: str, federation_list: [], hasFollows: bool, hasPosts: bool, send_threads: []): print('Creating test server: Bob on port ' + str(port)) if os.path.isdir(path): shutil.rmtree(path, ignore_errors=False, onerror=None) os.mkdir(path) os.chdir(path) shared_items_federated_domains = [] system_language = 'en' nickname = 'bob' http_prefix = 'http' proxy_type = None client_to_server = False password = 'bobpass' max_replies = 64 domain_max_posts_per_day = 1000 account_max_posts_per_day = 1000 allow_deletion = True low_bandwidth = True privateKeyPem, publicKeyPem, person, wfEndpoint = \ create_person(path, nickname, domain, port, http_prefix, True, False, password) delete_all_posts(path, nickname, domain, 'inbox') delete_all_posts(path, nickname, domain, 'outbox') if hasFollows and aliceAddress: follow_person(path, nickname, domain, 'alice', aliceAddress, federation_list, False, False) add_follower_of_person(path, nickname, domain, 'alice', aliceAddress, federation_list, False, False) if hasPosts: testFollowersOnly = False testSaveToFile = True testCommentsEnabled = True testAttachImageFilename = None testImageDescription = None testMediaType = None testCity = 'London, England' testInReplyTo = None testInReplyToAtomUri = None testSubject = None testSchedulePost = False testEventDate = None testEventTime = None testLocation = None testIsArticle = False conversationId = None content_license_url = 'https://creativecommons.org/licenses/by/4.0' create_public_post(path, nickname, domain, port, http_prefix, "It's your life, live it your way.", testFollowersOnly, testSaveToFile, client_to_server, testCommentsEnabled, testAttachImageFilename, testMediaType, testImageDescription, testCity, testInReplyTo, testInReplyToAtomUri, testSubject, testSchedulePost, testEventDate, testEventTime, testLocation, testIsArticle, system_language, conversationId, low_bandwidth, content_license_url) create_public_post(path, nickname, domain, port, http_prefix, "One of the things I've realised is that " + "I am very simple", testFollowersOnly, testSaveToFile, client_to_server, testCommentsEnabled, testAttachImageFilename, testMediaType, testImageDescription, testCity, testInReplyTo, testInReplyToAtomUri, testSubject, testSchedulePost, testEventDate, testEventTime, testLocation, testIsArticle, system_language, conversationId, low_bandwidth, content_license_url) create_public_post(path, nickname, domain, port, http_prefix, "Quantum physics is a bit of a passion of mine", testFollowersOnly, testSaveToFile, client_to_server, testCommentsEnabled, testAttachImageFilename, testMediaType, testImageDescription, testCity, testInReplyTo, testInReplyToAtomUri, testSubject, testSchedulePost, testEventDate, testEventTime, testLocation, testIsArticle, system_language, conversationId, low_bandwidth, content_license_url) regenerate_index_for_box(path, nickname, domain, 'outbox') global testServerBobRunning testServerBobRunning = True max_mentions = 10 max_emoji = 10 onion_domain = None i2p_domain = None allow_local_network_access = True max_newswire_posts = 20 dormant_months = 3 send_threads_timeout_mins = 30 max_followers = 10 verify_all_signatures = True broch_mode = False show_node_info_accounts = True show_node_info_version = True city = 'London, England' log_login_failures = False user_agents_blocked = [] max_like_count = 10 default_reply_interval_hrs = 9999999999 lists_enabled = '' content_license_url = 'https://creativecommons.org/licenses/by/4.0' print('Server running: Bob') run_daemon(content_license_url, lists_enabled, default_reply_interval_hrs, low_bandwidth, max_like_count, shared_items_federated_domains, user_agents_blocked, log_login_failures, city, show_node_info_accounts, show_node_info_version, broch_mode, verify_all_signatures, send_threads_timeout_mins, dormant_months, max_newswire_posts, allow_local_network_access, 2048, False, True, False, False, True, max_followers, 0, 100, 1024, 5, False, 0, False, 1, False, False, False, 5, True, True, 'en', __version__, "instance_id", False, path, domain, onion_domain, i2p_domain, None, None, port, port, http_prefix, federation_list, max_mentions, max_emoji, False, proxy_type, max_replies, domain_max_posts_per_day, account_max_posts_per_day, allow_deletion, True, True, False, send_threads, False) def create_server_eve(path: str, domain: str, port: int, federation_list: [], hasFollows: bool, hasPosts: bool, send_threads: []): print('Creating test server: Eve on port ' + str(port)) if os.path.isdir(path): shutil.rmtree(path, ignore_errors=False, onerror=None) os.mkdir(path) os.chdir(path) shared_items_federated_domains = [] nickname = 'eve' http_prefix = 'http' proxy_type = None password = 'evepass' max_replies = 64 allow_deletion = True privateKeyPem, publicKeyPem, person, wfEndpoint = \ create_person(path, nickname, domain, port, http_prefix, True, False, password) delete_all_posts(path, nickname, domain, 'inbox') delete_all_posts(path, nickname, domain, 'outbox') global testServerEveRunning testServerEveRunning = True max_mentions = 10 max_emoji = 10 onion_domain = None i2p_domain = None allow_local_network_access = True max_newswire_posts = 20 dormant_months = 3 send_threads_timeout_mins = 30 max_followers = 10 verify_all_signatures = True broch_mode = False show_node_info_accounts = True show_node_info_version = True city = 'London, England' log_login_failures = False user_agents_blocked = [] max_like_count = 10 low_bandwidth = True default_reply_interval_hrs = 9999999999 lists_enabled = '' content_license_url = 'https://creativecommons.org/licenses/by/4.0' print('Server running: Eve') run_daemon(content_license_url, lists_enabled, default_reply_interval_hrs, low_bandwidth, max_like_count, shared_items_federated_domains, user_agents_blocked, log_login_failures, city, show_node_info_accounts, show_node_info_version, broch_mode, verify_all_signatures, send_threads_timeout_mins, dormant_months, max_newswire_posts, allow_local_network_access, 2048, False, True, False, False, True, max_followers, 0, 100, 1024, 5, False, 0, False, 1, False, False, False, 5, True, True, 'en', __version__, "instance_id", False, path, domain, onion_domain, i2p_domain, None, None, port, port, http_prefix, federation_list, max_mentions, max_emoji, False, proxy_type, max_replies, allow_deletion, True, True, False, send_threads, False) def create_server_group(path: str, domain: str, port: int, federation_list: [], hasFollows: bool, hasPosts: bool, send_threads: []): print('Creating test server: Group on port ' + str(port)) if os.path.isdir(path): shutil.rmtree(path, ignore_errors=False, onerror=None) os.mkdir(path) os.chdir(path) shared_items_federated_domains = [] # system_language = 'en' nickname = 'testgroup' http_prefix = 'http' proxy_type = None password = 'testgrouppass' max_replies = 64 domain_max_posts_per_day = 1000 account_max_posts_per_day = 1000 allow_deletion = True privateKeyPem, publicKeyPem, person, wfEndpoint = \ create_group(path, nickname, domain, port, http_prefix, True, password) delete_all_posts(path, nickname, domain, 'inbox') delete_all_posts(path, nickname, domain, 'outbox') global testServerGroupRunning testServerGroupRunning = True max_mentions = 10 max_emoji = 10 onion_domain = None i2p_domain = None allow_local_network_access = True max_newswire_posts = 20 dormant_months = 3 send_threads_timeout_mins = 30 max_followers = 10 verify_all_signatures = True broch_mode = False show_node_info_accounts = True show_node_info_version = True city = 'London, England' log_login_failures = False user_agents_blocked = [] max_like_count = 10 low_bandwidth = True default_reply_interval_hrs = 9999999999 lists_enabled = '' content_license_url = 'https://creativecommons.org/licenses/by/4.0' print('Server running: Group') run_daemon(content_license_url, lists_enabled, default_reply_interval_hrs, low_bandwidth, max_like_count, shared_items_federated_domains, user_agents_blocked, log_login_failures, city, show_node_info_accounts, show_node_info_version, broch_mode, verify_all_signatures, send_threads_timeout_mins, dormant_months, max_newswire_posts, allow_local_network_access, 2048, False, True, False, False, True, max_followers, 0, 100, 1024, 5, False, 0, False, 1, False, False, False, 5, True, True, 'en', __version__, "instance_id", False, path, domain, onion_domain, i2p_domain, None, None, port, port, http_prefix, federation_list, max_mentions, max_emoji, False, proxy_type, max_replies, domain_max_posts_per_day, account_max_posts_per_day, allow_deletion, True, True, False, send_threads, False) def test_post_message_between_servers(base_dir: str) -> None: print('Testing sending message from one server to the inbox of another') global testServerAliceRunning global testServerBobRunning testServerAliceRunning = False testServerBobRunning = False system_language = 'en' http_prefix = 'http' proxy_type = None content_license_url = 'https://creativecommons.org/licenses/by/4.0' if os.path.isdir(base_dir + '/.tests'): shutil.rmtree(base_dir + '/.tests', ignore_errors=False, onerror=None) os.mkdir(base_dir + '/.tests') # create the servers aliceDir = base_dir + '/.tests/alice' aliceDomain = '127.0.0.50' alicePort = 61935 aliceAddress = aliceDomain + ':' + str(alicePort) bobDir = base_dir + '/.tests/bob' bobDomain = '127.0.0.100' bobPort = 61936 federation_list = [bobDomain, aliceDomain] aliceSendThreads = [] bobSendThreads = [] bobAddress = bobDomain + ':' + str(bobPort) global thrAlice if thrAlice: while thrAlice.is_alive(): thrAlice.stop() time.sleep(1) thrAlice.kill() thrAlice = \ thread_with_trace(target=create_server_alice, args=(aliceDir, aliceDomain, alicePort, bobAddress, federation_list, False, False, aliceSendThreads), daemon=True) global thrBob if thrBob: while thrBob.is_alive(): thrBob.stop() time.sleep(1) thrBob.kill() thrBob = \ thread_with_trace(target=create_server_bob, args=(bobDir, bobDomain, bobPort, aliceAddress, federation_list, False, False, bobSendThreads), daemon=True) thrAlice.start() thrBob.start() assert thrAlice.is_alive() is True assert thrBob.is_alive() is True # wait for both servers to be running while not (testServerAliceRunning and testServerBobRunning): time.sleep(1) time.sleep(1) print('\n\n*******************************************************') print('Alice sends to Bob') os.chdir(aliceDir) sessionAlice = create_session(proxy_type) inReplyTo = None inReplyToAtomUri = None subject = None alicePostLog = [] followersOnly = False saveToFile = True client_to_server = False ccUrl = None alicePersonCache = {} aliceCachedWebfingers = {} aliceSharedItemsFederatedDomains = [] aliceSharedItemFederationTokens = {} attachedImageFilename = base_dir + '/img/logo.png' testImageWidth, testImageHeight = \ get_image_dimensions(attachedImageFilename) assert testImageWidth assert testImageHeight mediaType = get_attachment_media_type(attachedImageFilename) attachedImageDescription = 'Logo' isArticle = False city = 'London, England' # nothing in Alice's outbox outboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/outbox' assert len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) == 0 low_bandwidth = False signing_priv_key_pem = None sendResult = \ send_post(signing_priv_key_pem, __version__, sessionAlice, aliceDir, 'alice', aliceDomain, alicePort, 'bob', bobDomain, bobPort, ccUrl, http_prefix, 'Why is a mouse when it spins? ' + 'यह एक परीक्षण है #sillyquestion', followersOnly, saveToFile, client_to_server, True, attachedImageFilename, mediaType, attachedImageDescription, city, federation_list, aliceSendThreads, alicePostLog, aliceCachedWebfingers, alicePersonCache, isArticle, system_language, aliceSharedItemsFederatedDomains, aliceSharedItemFederationTokens, low_bandwidth, content_license_url, inReplyTo, inReplyToAtomUri, subject) print('sendResult: ' + str(sendResult)) queuePath = bobDir + '/accounts/bob@' + bobDomain + '/queue' inboxPath = bobDir + '/accounts/bob@' + bobDomain + '/inbox' mPath = get_media_path() mediaPath = aliceDir + '/' + mPath for i in range(30): if os.path.isdir(inboxPath): if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) > 0: if len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) == 1: if len([name for name in os.listdir(mediaPath) if os.path.isfile(os.path.join(mediaPath, name))]) > 0: if len([name for name in os.listdir(queuePath) if os.path.isfile(os.path.join(queuePath, name))]) == 0: break time.sleep(1) # check that a news account exists newsActorDir = aliceDir + '/accounts/news@' + aliceDomain print("newsActorDir: " + newsActorDir) assert os.path.isdir(newsActorDir) newsActorFile = newsActorDir + '.json' assert os.path.isfile(newsActorFile) newsActorJson = load_json(newsActorFile) assert newsActorJson assert newsActorJson.get("id") # check the id of the news actor print('News actor Id: ' + newsActorJson["id"]) assert (newsActorJson["id"] == http_prefix + '://' + aliceAddress + '/users/news') # Image attachment created assert len([name for name in os.listdir(mediaPath) if os.path.isfile(os.path.join(mediaPath, name))]) > 0 # inbox item created assert len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) == 1 # queue item removed testval = len([name for name in os.listdir(queuePath) if os.path.isfile(os.path.join(queuePath, name))]) print('queuePath: ' + queuePath + ' '+str(testval)) assert testval == 0 assert valid_inbox(bobDir, 'bob', bobDomain) assert valid_inbox_filenames(bobDir, 'bob', bobDomain, aliceDomain, alicePort) print('Check that message received from Alice contains the expected text') for name in os.listdir(inboxPath): filename = os.path.join(inboxPath, name) assert os.path.isfile(filename) receivedJson = load_json(filename, 0) if receivedJson: pprint(receivedJson['object']['content']) assert receivedJson assert 'Why is a mouse when it spins?' in \ receivedJson['object']['content'] assert 'Why is a mouse when it spins?' in \ receivedJson['object']['contentMap'][system_language] assert 'यह एक परीक्षण है' in receivedJson['object']['content'] print('Check that message received from Alice contains an attachment') assert receivedJson['object']['attachment'] assert len(receivedJson['object']['attachment']) == 1 attached = receivedJson['object']['attachment'][0] pprint(attached) assert attached.get('type') assert attached.get('url') assert attached['mediaType'] == 'image/png' if '/system/media_attachments/files/' not in attached['url']: print(attached['url']) assert '/system/media_attachments/files/' in attached['url'] assert attached['url'].endswith('.png') assert attached.get('width') assert attached.get('height') assert attached['width'] > 0 assert attached['height'] > 0 print('\n\n*******************************************************') print("Bob likes Alice's post") aliceDomainStr = aliceDomain + ':' + str(alicePort) add_follower_of_person(bobDir, 'bob', bobDomain, 'alice', aliceDomainStr, federation_list, False, False) bobDomainStr = bobDomain + ':' + str(bobPort) follow_person(aliceDir, 'alice', aliceDomain, 'bob', bobDomainStr, federation_list, False, False) sessionBob = create_session(proxy_type) bobPostLog = [] bobPersonCache = {} bobCachedWebfingers = {} statusNumber = None outboxPostFilename = None outboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/outbox' for name in os.listdir(outboxPath): if '#statuses#' in name: statusNumber = \ int(name.split('#statuses#')[1].replace('.json', '')) outboxPostFilename = outboxPath + '/' + name assert statusNumber > 0 assert outboxPostFilename assert like_post({}, sessionBob, bobDir, federation_list, 'bob', bobDomain, bobPort, http_prefix, 'alice', aliceDomain, alicePort, [], statusNumber, False, bobSendThreads, bobPostLog, bobPersonCache, bobCachedWebfingers, True, __version__, signing_priv_key_pem) for i in range(20): if 'likes' in open(outboxPostFilename).read(): break time.sleep(1) alicePostJson = load_json(outboxPostFilename, 0) if alicePostJson: pprint(alicePostJson) assert 'likes' in open(outboxPostFilename).read() print('\n\n*******************************************************') print("Bob reacts to Alice's post") assert reaction_post({}, sessionBob, bobDir, federation_list, 'bob', bobDomain, bobPort, http_prefix, 'alice', aliceDomain, alicePort, [], statusNumber, '😀', False, bobSendThreads, bobPostLog, bobPersonCache, bobCachedWebfingers, True, __version__, signing_priv_key_pem) for i in range(20): if 'reactions' in open(outboxPostFilename).read(): break time.sleep(1) alicePostJson = load_json(outboxPostFilename, 0) if alicePostJson: pprint(alicePostJson) assert 'reactions' in open(outboxPostFilename).read() print('\n\n*******************************************************') print("Bob repeats Alice's post") objectUrl = \ http_prefix + '://' + aliceDomain + ':' + str(alicePort) + \ '/users/alice/statuses/' + str(statusNumber) inboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/inbox' outboxPath = bobDir + '/accounts/bob@' + bobDomain + '/outbox' outboxBeforeAnnounceCount = \ len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) beforeAnnounceCount = \ len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) print('inbox items before announce: ' + str(beforeAnnounceCount)) print('outbox items before announce: ' + str(outboxBeforeAnnounceCount)) assert outboxBeforeAnnounceCount == 0 assert beforeAnnounceCount == 0 announce_public(sessionBob, bobDir, federation_list, 'bob', bobDomain, bobPort, http_prefix, objectUrl, False, bobSendThreads, bobPostLog, bobPersonCache, bobCachedWebfingers, True, __version__, signing_priv_key_pem) announceMessageArrived = False outboxMessageArrived = False for i in range(10): time.sleep(1) if not os.path.isdir(inboxPath): continue if len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) > 0: outboxMessageArrived = True print('Announce created by Bob') if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) > 0: announceMessageArrived = True print('Announce message sent to Alice!') if announceMessageArrived and outboxMessageArrived: break afterAnnounceCount = \ len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) outboxAfterAnnounceCount = \ len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) print('inbox items after announce: ' + str(afterAnnounceCount)) print('outbox items after announce: ' + str(outboxAfterAnnounceCount)) assert afterAnnounceCount == beforeAnnounceCount+1 assert outboxAfterAnnounceCount == outboxBeforeAnnounceCount + 1 # stop the servers thrAlice.kill() thrAlice.join() assert thrAlice.is_alive() is False thrBob.kill() thrBob.join() assert thrBob.is_alive() is False os.chdir(base_dir) shutil.rmtree(aliceDir, ignore_errors=False, onerror=None) shutil.rmtree(bobDir, ignore_errors=False, onerror=None) def test_follow_between_servers(base_dir: str) -> None: print('Testing sending a follow request from one server to another') global testServerAliceRunning global testServerBobRunning testServerAliceRunning = False testServerBobRunning = False system_language = 'en' http_prefix = 'http' proxy_type = None federation_list = [] content_license_url = 'https://creativecommons.org/licenses/by/4.0' if os.path.isdir(base_dir + '/.tests'): shutil.rmtree(base_dir + '/.tests', ignore_errors=False, onerror=None) os.mkdir(base_dir + '/.tests') # create the servers aliceDir = base_dir + '/.tests/alice' aliceDomain = '127.0.0.47' alicePort = 61935 aliceSendThreads = [] aliceAddress = aliceDomain + ':' + str(alicePort) bobDir = base_dir + '/.tests/bob' bobDomain = '127.0.0.79' bobPort = 61936 bobSendThreads = [] bobAddress = bobDomain + ':' + str(bobPort) global thrAlice if thrAlice: while thrAlice.is_alive(): thrAlice.stop() time.sleep(1) thrAlice.kill() thrAlice = \ thread_with_trace(target=create_server_alice, args=(aliceDir, aliceDomain, alicePort, bobAddress, federation_list, False, False, aliceSendThreads), daemon=True) global thrBob if thrBob: while thrBob.is_alive(): thrBob.stop() time.sleep(1) thrBob.kill() thrBob = \ thread_with_trace(target=create_server_bob, args=(bobDir, bobDomain, bobPort, aliceAddress, federation_list, False, False, bobSendThreads), daemon=True) thrAlice.start() thrBob.start() assert thrAlice.is_alive() is True assert thrBob.is_alive() is True # wait for all servers to be running ctr = 0 while not (testServerAliceRunning and testServerBobRunning): time.sleep(1) ctr += 1 if ctr > 60: break print('Alice online: ' + str(testServerAliceRunning)) print('Bob online: ' + str(testServerBobRunning)) assert ctr <= 60 time.sleep(1) # In the beginning all was calm and there were no follows print('*********************************************************') print('Alice sends a follow request to Bob') os.chdir(aliceDir) sessionAlice = create_session(proxy_type) inReplyTo = None inReplyToAtomUri = None subject = None alicePostLog = [] followersOnly = False saveToFile = True client_to_server = False ccUrl = None alicePersonCache = {} aliceCachedWebfingers = {} alicePostLog = [] bobActor = http_prefix + '://' + bobAddress + '/users/bob' signing_priv_key_pem = None sendResult = \ send_follow_request(sessionAlice, aliceDir, 'alice', aliceDomain, alicePort, http_prefix, 'bob', bobDomain, bobActor, bobPort, http_prefix, client_to_server, federation_list, aliceSendThreads, alicePostLog, aliceCachedWebfingers, alicePersonCache, True, __version__, signing_priv_key_pem) print('sendResult: ' + str(sendResult)) for t in range(16): if os.path.isfile(bobDir + '/accounts/bob@' + bobDomain + '/followers.txt'): if os.path.isfile(aliceDir + '/accounts/alice@' + aliceDomain + '/following.txt'): if os.path.isfile(aliceDir + '/accounts/alice@' + aliceDomain + '/followingCalendar.txt'): break time.sleep(1) assert valid_inbox(bobDir, 'bob', bobDomain) assert valid_inbox_filenames(bobDir, 'bob', bobDomain, aliceDomain, alicePort) assert 'alice@' + aliceDomain in open(bobDir + '/accounts/bob@' + bobDomain + '/followers.txt').read() assert 'bob@' + bobDomain in open(aliceDir + '/accounts/alice@' + aliceDomain + '/following.txt').read() assert 'bob@' + bobDomain in open(aliceDir + '/accounts/alice@' + aliceDomain + '/followingCalendar.txt').read() assert not is_group_actor(aliceDir, bobActor, alicePersonCache) assert not is_group_account(aliceDir, 'alice', aliceDomain) print('\n\n*********************************************************') print('Alice sends a message to Bob') alicePostLog = [] alicePersonCache = {} aliceCachedWebfingers = {} aliceSharedItemsFederatedDomains = [] aliceSharedItemFederationTokens = {} alicePostLog = [] isArticle = False city = 'London, England' low_bandwidth = False signing_priv_key_pem = None sendResult = \ send_post(signing_priv_key_pem, __version__, sessionAlice, aliceDir, 'alice', aliceDomain, alicePort, 'bob', bobDomain, bobPort, ccUrl, http_prefix, 'Alice message', followersOnly, saveToFile, client_to_server, True, None, None, None, city, federation_list, aliceSendThreads, alicePostLog, aliceCachedWebfingers, alicePersonCache, isArticle, system_language, aliceSharedItemsFederatedDomains, aliceSharedItemFederationTokens, low_bandwidth, content_license_url, inReplyTo, inReplyToAtomUri, subject) print('sendResult: ' + str(sendResult)) queuePath = bobDir + '/accounts/bob@' + bobDomain + '/queue' inboxPath = bobDir + '/accounts/bob@' + bobDomain + '/inbox' aliceMessageArrived = False for i in range(20): time.sleep(1) if os.path.isdir(inboxPath): if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) > 0: aliceMessageArrived = True print('Alice message sent to Bob!') break assert aliceMessageArrived is True print('Message from Alice to Bob succeeded') # stop the servers thrAlice.kill() thrAlice.join() assert thrAlice.is_alive() is False thrBob.kill() thrBob.join() assert thrBob.is_alive() is False # queue item removed time.sleep(4) assert len([name for name in os.listdir(queuePath) if os.path.isfile(os.path.join(queuePath, name))]) == 0 os.chdir(base_dir) shutil.rmtree(base_dir + '/.tests', ignore_errors=False, onerror=None) def test_shared_items_federation(base_dir: str) -> None: print('Testing federation of shared items between Alice and Bob') global testServerAliceRunning global testServerBobRunning testServerAliceRunning = False testServerBobRunning = False system_language = 'en' http_prefix = 'http' proxy_type = None federation_list = [] content_license_url = 'https://creativecommons.org/licenses/by/4.0' if os.path.isdir(base_dir + '/.tests'): shutil.rmtree(base_dir + '/.tests', ignore_errors=False, onerror=None) os.mkdir(base_dir + '/.tests') # create the servers aliceDir = base_dir + '/.tests/alice' aliceDomain = '127.0.0.74' alicePort = 61917 aliceSendThreads = [] aliceAddress = aliceDomain + ':' + str(alicePort) bobDir = base_dir + '/.tests/bob' bobDomain = '127.0.0.81' bobPort = 61983 bobSendThreads = [] bobAddress = bobDomain + ':' + str(bobPort) bobPassword = 'bobpass' bobCachedWebfingers = {} bobPersonCache = {} global thrAlice if thrAlice: while thrAlice.is_alive(): thrAlice.stop() time.sleep(1) thrAlice.kill() thrAlice = \ thread_with_trace(target=create_server_alice, args=(aliceDir, aliceDomain, alicePort, bobAddress, federation_list, False, False, aliceSendThreads), daemon=True) global thrBob if thrBob: while thrBob.is_alive(): thrBob.stop() time.sleep(1) thrBob.kill() thrBob = \ thread_with_trace(target=create_server_bob, args=(bobDir, bobDomain, bobPort, aliceAddress, federation_list, False, False, bobSendThreads), daemon=True) thrAlice.start() thrBob.start() assert thrAlice.is_alive() is True assert thrBob.is_alive() is True # wait for all servers to be running ctr = 0 while not (testServerAliceRunning and testServerBobRunning): time.sleep(1) ctr += 1 if ctr > 60: break print('Alice online: ' + str(testServerAliceRunning)) print('Bob online: ' + str(testServerBobRunning)) assert ctr <= 60 time.sleep(1) signing_priv_key_pem = None sessionClient = create_session(proxy_type) # Get Bob's instance actor print('\n\n*********************************************************') print("Test Bob's instance actor") profileStr = 'https://www.w3.org/ns/activitystreams' testHeaders = { 'host': bobAddress, 'Accept': 'application/ld+json; profile="' + profileStr + '"' } bobInstanceActorJson = \ get_json(signing_priv_key_pem, sessionClient, 'http://' + bobAddress + '/@actor', testHeaders, {}, True, __version__, 'http', 'somedomain.or.other', 10, True) assert bobInstanceActorJson pprint(bobInstanceActorJson) assert bobInstanceActorJson['name'] == 'ACTOR' # In the beginning all was calm and there were no follows print('\n\n*********************************************************') print("Alice and Bob agree to share items catalogs") assert os.path.isdir(aliceDir) assert os.path.isdir(bobDir) set_config_param(aliceDir, 'sharedItemsFederatedDomains', bobAddress) set_config_param(bobDir, 'sharedItemsFederatedDomains', aliceAddress) print('*********************************************************') print('Alice sends a follow request to Bob') os.chdir(aliceDir) sessionAlice = create_session(proxy_type) inReplyTo = None inReplyToAtomUri = None subject = None alicePostLog = [] followersOnly = False saveToFile = True client_to_server = False ccUrl = None alicePersonCache = {} aliceCachedWebfingers = {} alicePostLog = [] bobActor = http_prefix + '://' + bobAddress + '/users/bob' sendResult = \ send_follow_request(sessionAlice, aliceDir, 'alice', aliceDomain, alicePort, http_prefix, 'bob', bobDomain, bobActor, bobPort, http_prefix, client_to_server, federation_list, aliceSendThreads, alicePostLog, aliceCachedWebfingers, alicePersonCache, True, __version__, signing_priv_key_pem) print('sendResult: ' + str(sendResult)) for t in range(16): if os.path.isfile(bobDir + '/accounts/bob@' + bobDomain + '/followers.txt'): if os.path.isfile(aliceDir + '/accounts/alice@' + aliceDomain + '/following.txt'): if os.path.isfile(aliceDir + '/accounts/alice@' + aliceDomain + '/followingCalendar.txt'): break time.sleep(1) assert valid_inbox(bobDir, 'bob', bobDomain) assert valid_inbox_filenames(bobDir, 'bob', bobDomain, aliceDomain, alicePort) assert 'alice@' + aliceDomain in open(bobDir + '/accounts/bob@' + bobDomain + '/followers.txt').read() assert 'bob@' + bobDomain in open(aliceDir + '/accounts/alice@' + aliceDomain + '/following.txt').read() assert 'bob@' + bobDomain in open(aliceDir + '/accounts/alice@' + aliceDomain + '/followingCalendar.txt').read() assert not is_group_actor(aliceDir, bobActor, alicePersonCache) assert not is_group_account(bobDir, 'bob', bobDomain) print('\n\n*********************************************************') print('Bob publishes some shared items') if os.path.isdir(bobDir + '/ontology'): shutil.rmtree(bobDir + '/ontology', ignore_errors=False, onerror=None) os.mkdir(bobDir + '/ontology') copyfile(base_dir + '/img/logo.png', bobDir + '/logo.png') copyfile(base_dir + '/ontology/foodTypes.json', bobDir + '/ontology/foodTypes.json') copyfile(base_dir + '/ontology/toolTypes.json', bobDir + '/ontology/toolTypes.json') copyfile(base_dir + '/ontology/clothesTypes.json', bobDir + '/ontology/clothesTypes.json') copyfile(base_dir + '/ontology/medicalTypes.json', bobDir + '/ontology/medicalTypes.json') copyfile(base_dir + '/ontology/accommodationTypes.json', bobDir + '/ontology/accommodationTypes.json') assert os.path.isfile(bobDir + '/logo.png') assert os.path.isfile(bobDir + '/ontology/foodTypes.json') assert os.path.isfile(bobDir + '/ontology/toolTypes.json') assert os.path.isfile(bobDir + '/ontology/clothesTypes.json') assert os.path.isfile(bobDir + '/ontology/medicalTypes.json') assert os.path.isfile(bobDir + '/ontology/accommodationTypes.json') sharedItemName = 'cheddar' sharedItemDescription = 'Some cheese' sharedItemImageFilename = 'logo.png' sharedItemQty = 1 sharedItemType = 'Cheese' sharedItemCategory = 'Food' sharedItemLocation = "Bob's location" sharedItemDuration = "10 days" sharedItemPrice = "1.30" sharedItemCurrency = "EUR" signing_priv_key_pem = None sessionBob = create_session(proxy_type) shareJson = \ send_share_via_server(bobDir, sessionBob, 'bob', bobPassword, bobDomain, bobPort, http_prefix, sharedItemName, sharedItemDescription, sharedItemImageFilename, sharedItemQty, sharedItemType, sharedItemCategory, sharedItemLocation, sharedItemDuration, bobCachedWebfingers, bobPersonCache, True, __version__, sharedItemPrice, sharedItemCurrency, signing_priv_key_pem) assert shareJson assert isinstance(shareJson, dict) sharedItemName = 'Epicyon T-shirt' sharedItemDescription = 'A fashionable item' sharedItemImageFilename = 'logo.png' sharedItemQty = 1 sharedItemType = 'T-Shirt' sharedItemCategory = 'Clothes' sharedItemLocation = "Bob's location" sharedItemDuration = "5 days" sharedItemPrice = "0" sharedItemCurrency = "EUR" shareJson = \ send_share_via_server(bobDir, sessionBob, 'bob', bobPassword, bobDomain, bobPort, http_prefix, sharedItemName, sharedItemDescription, sharedItemImageFilename, sharedItemQty, sharedItemType, sharedItemCategory, sharedItemLocation, sharedItemDuration, bobCachedWebfingers, bobPersonCache, True, __version__, sharedItemPrice, sharedItemCurrency, signing_priv_key_pem) assert shareJson assert isinstance(shareJson, dict) sharedItemName = 'Soldering iron' sharedItemDescription = 'A soldering iron' sharedItemImageFilename = 'logo.png' sharedItemQty = 1 sharedItemType = 'Soldering iron' sharedItemCategory = 'Tools' sharedItemLocation = "Bob's location" sharedItemDuration = "9 days" sharedItemPrice = "10.00" sharedItemCurrency = "EUR" shareJson = \ send_share_via_server(bobDir, sessionBob, 'bob', bobPassword, bobDomain, bobPort, http_prefix, sharedItemName, sharedItemDescription, sharedItemImageFilename, sharedItemQty, sharedItemType, sharedItemCategory, sharedItemLocation, sharedItemDuration, bobCachedWebfingers, bobPersonCache, True, __version__, sharedItemPrice, sharedItemCurrency, signing_priv_key_pem) assert shareJson assert isinstance(shareJson, dict) time.sleep(2) print('\n\n*********************************************************') print('Bob has a shares.json file containing the uploaded items') sharesFilename = bobDir + '/accounts/bob@' + bobDomain + '/shares.json' assert os.path.isfile(sharesFilename) sharesJson = load_json(sharesFilename) assert sharesJson pprint(sharesJson) assert len(sharesJson.items()) == 3 for itemID, item in sharesJson.items(): if not item.get('dfcId'): pprint(item) print(itemID + ' does not have dfcId field') assert item.get('dfcId') print('\n\n*********************************************************') print('Bob can read the shared items catalog on his own instance') signing_priv_key_pem = None catalogJson = \ get_shared_items_catalog_via_server(bobDir, sessionBob, 'bob', bobPassword, bobDomain, bobPort, http_prefix, True, signing_priv_key_pem) assert catalogJson pprint(catalogJson) assert 'DFC:supplies' in catalogJson assert len(catalogJson.get('DFC:supplies')) == 3 print('\n\n*********************************************************') print('Alice sends a message to Bob') aliceTokensFilename = \ aliceDir + '/accounts/sharedItemsFederationTokens.json' assert os.path.isfile(aliceTokensFilename) aliceSharedItemFederationTokens = load_json(aliceTokensFilename) assert aliceSharedItemFederationTokens print('Alice shared item federation tokens:') pprint(aliceSharedItemFederationTokens) assert len(aliceSharedItemFederationTokens.items()) > 0 for hostStr, token in aliceSharedItemFederationTokens.items(): assert ':' in hostStr alicePostLog = [] alicePersonCache = {} aliceCachedWebfingers = {} aliceSharedItemsFederatedDomains = [bobAddress] alicePostLog = [] isArticle = False city = 'London, England' low_bandwidth = False signing_priv_key_pem = None sendResult = \ send_post(signing_priv_key_pem, __version__, sessionAlice, aliceDir, 'alice', aliceDomain, alicePort, 'bob', bobDomain, bobPort, ccUrl, http_prefix, 'Alice message', followersOnly, saveToFile, client_to_server, True, None, None, None, city, federation_list, aliceSendThreads, alicePostLog, aliceCachedWebfingers, alicePersonCache, isArticle, system_language, aliceSharedItemsFederatedDomains, aliceSharedItemFederationTokens, low_bandwidth, content_license_url, True, inReplyTo, inReplyToAtomUri, subject) print('sendResult: ' + str(sendResult)) queuePath = bobDir + '/accounts/bob@' + bobDomain + '/queue' inboxPath = bobDir + '/accounts/bob@' + bobDomain + '/inbox' aliceMessageArrived = False for i in range(20): time.sleep(1) if os.path.isdir(inboxPath): if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) > 0: aliceMessageArrived = True print('Alice message sent to Bob!') break assert aliceMessageArrived is True print('Message from Alice to Bob succeeded') print('\n\n*********************************************************') print('Check that Alice received the shared items authorization') print('token from Bob') aliceTokensFilename = \ aliceDir + '/accounts/sharedItemsFederationTokens.json' bobTokensFilename = \ bobDir + '/accounts/sharedItemsFederationTokens.json' assert os.path.isfile(aliceTokensFilename) assert os.path.isfile(bobTokensFilename) aliceTokens = load_json(aliceTokensFilename) assert aliceTokens for hostStr, token in aliceTokens.items(): assert ':' in hostStr assert aliceTokens.get(aliceAddress) print('Alice tokens') pprint(aliceTokens) bobTokens = load_json(bobTokensFilename) assert bobTokens for hostStr, token in bobTokens.items(): assert ':' in hostStr assert bobTokens.get(bobAddress) print("Check that Bob now has Alice's token") assert bobTokens.get(aliceAddress) print('Bob tokens') pprint(bobTokens) print('\n\n*********************************************************') print('Alice can read the federated shared items catalog of Bob') headers = { 'Origin': aliceAddress, 'Authorization': bobTokens[bobAddress], 'host': bobAddress, 'Accept': 'application/json' } url = http_prefix + '://' + bobAddress + '/catalog' signing_priv_key_pem = None catalogJson = get_json(signing_priv_key_pem, sessionAlice, url, headers, None, True) assert catalogJson pprint(catalogJson) assert 'DFC:supplies' in catalogJson assert len(catalogJson.get('DFC:supplies')) == 3 # queue item removed ctr = 0 while len([name for name in os.listdir(queuePath) if os.path.isfile(os.path.join(queuePath, name))]) > 0: ctr += 1 if ctr > 10: break time.sleep(1) # assert len([name for name in os.listdir(queuePath) # if os.path.isfile(os.path.join(queuePath, name))]) == 0 # stop the servers thrAlice.kill() thrAlice.join() assert thrAlice.is_alive() is False thrBob.kill() thrBob.join() assert thrBob.is_alive() is False os.chdir(base_dir) shutil.rmtree(base_dir + '/.tests', ignore_errors=False, onerror=None) print('Testing federation of shared items between ' + 'Alice and Bob is complete') def test_group_follow(base_dir: str) -> None: print('Testing following of a group') global testServerAliceRunning global testServerBobRunning global testServerGroupRunning system_language = 'en' testServerAliceRunning = False testServerBobRunning = False testServerGroupRunning = False # system_language = 'en' http_prefix = 'http' proxy_type = None federation_list = [] content_license_url = 'https://creativecommons.org/licenses/by/4.0' if os.path.isdir(base_dir + '/.tests'): shutil.rmtree(base_dir + '/.tests', ignore_errors=False, onerror=None) os.mkdir(base_dir + '/.tests') # create the servers aliceDir = base_dir + '/.tests/alice' aliceDomain = '127.0.0.57' alicePort = 61927 aliceSendThreads = [] aliceAddress = aliceDomain + ':' + str(alicePort) bobDir = base_dir + '/.tests/bob' bobDomain = '127.0.0.59' bobPort = 61814 bobSendThreads = [] # bobAddress = bobDomain + ':' + str(bobPort) testgroupDir = base_dir + '/.tests/testgroup' testgroupDomain = '127.0.0.63' testgroupPort = 61925 testgroupSendThreads = [] testgroupAddress = testgroupDomain + ':' + str(testgroupPort) global thrAlice if thrAlice: while thrAlice.is_alive(): thrAlice.stop() time.sleep(1) thrAlice.kill() thrAlice = \ thread_with_trace(target=create_server_alice, args=(aliceDir, aliceDomain, alicePort, testgroupAddress, federation_list, False, True, aliceSendThreads), daemon=True) global thrBob if thrBob: while thrBob.is_alive(): thrBob.stop() time.sleep(1) thrBob.kill() thrBob = \ thread_with_trace(target=create_server_bob, args=(bobDir, bobDomain, bobPort, None, federation_list, False, False, bobSendThreads), daemon=True) global thrGroup if thrGroup: while thrGroup.is_alive(): thrGroup.stop() time.sleep(1) thrGroup.kill() thrGroup = \ thread_with_trace(target=create_server_group, args=(testgroupDir, testgroupDomain, testgroupPort, federation_list, False, False, testgroupSendThreads), daemon=True) thrAlice.start() thrBob.start() thrGroup.start() assert thrAlice.is_alive() is True assert thrBob.is_alive() is True assert thrGroup.is_alive() is True # wait for all servers to be running ctr = 0 while not (testServerAliceRunning and testServerBobRunning and testServerGroupRunning): time.sleep(1) ctr += 1 if ctr > 60: break print('Alice online: ' + str(testServerAliceRunning)) print('Bob online: ' + str(testServerBobRunning)) print('Test Group online: ' + str(testServerGroupRunning)) assert ctr <= 60 time.sleep(1) print('*********************************************************') print('Alice has some outbox posts') aliceOutbox = 'http://' + aliceAddress + '/users/alice/outbox' session = create_session(None) profileStr = 'https://www.w3.org/ns/activitystreams' asHeader = { 'Accept': 'application/ld+json; profile="' + profileStr + '"' } signing_priv_key_pem = None outboxJson = get_json(signing_priv_key_pem, session, aliceOutbox, asHeader, None, True, __version__, 'http', None) assert outboxJson pprint(outboxJson) assert outboxJson['type'] == 'OrderedCollection' assert 'first' in outboxJson firstPage = outboxJson['first'] assert 'totalItems' in outboxJson print('Alice outbox totalItems: ' + str(outboxJson['totalItems'])) assert outboxJson['totalItems'] == 3 outboxJson = get_json(signing_priv_key_pem, session, firstPage, asHeader, None, True, __version__, 'http', None) assert outboxJson pprint(outboxJson) assert 'orderedItems' in outboxJson assert outboxJson['type'] == 'OrderedCollectionPage' print('Alice outbox orderedItems: ' + str(len(outboxJson['orderedItems']))) assert len(outboxJson['orderedItems']) == 3 queuePath = \ testgroupDir + '/accounts/testgroup@' + testgroupDomain + '/queue' # In the beginning the test group had no followers print('*********************************************************') print('Alice sends a follow request to the test group') os.chdir(aliceDir) sessionAlice = create_session(proxy_type) inReplyTo = None inReplyToAtomUri = None subject = None alicePostLog = [] followersOnly = False saveToFile = True client_to_server = False ccUrl = None alicePersonCache = {} aliceCachedWebfingers = {} alicePostLog = [] # aliceActor = http_prefix + '://' + aliceAddress + '/users/alice' testgroupActor = \ http_prefix + '://' + testgroupAddress + '/users/testgroup' signing_priv_key_pem = None sendResult = \ send_follow_request(sessionAlice, aliceDir, 'alice', aliceDomain, alicePort, http_prefix, 'testgroup', testgroupDomain, testgroupActor, testgroupPort, http_prefix, client_to_server, federation_list, aliceSendThreads, alicePostLog, aliceCachedWebfingers, alicePersonCache, True, __version__, signing_priv_key_pem) print('sendResult: ' + str(sendResult)) aliceFollowingFilename = \ aliceDir + '/accounts/alice@' + aliceDomain + '/following.txt' aliceFollowingCalendarFilename = \ aliceDir + '/accounts/alice@' + aliceDomain + \ '/followingCalendar.txt' testgroupFollowersFilename = \ testgroupDir + '/accounts/testgroup@' + testgroupDomain + \ '/followers.txt' for t in range(16): if os.path.isfile(testgroupFollowersFilename): if os.path.isfile(aliceFollowingFilename): if os.path.isfile(aliceFollowingCalendarFilename): break time.sleep(1) assert valid_inbox(testgroupDir, 'testgroup', testgroupDomain) assert valid_inbox_filenames(testgroupDir, 'testgroup', testgroupDomain, aliceDomain, alicePort) assert 'alice@' + aliceDomain in open(testgroupFollowersFilename).read() assert '!alice@' + aliceDomain not in \ open(testgroupFollowersFilename).read() testgroupWebfingerFilename = \ testgroupDir + '/wfendpoints/testgroup@' + \ testgroupDomain + ':' + str(testgroupPort) + '.json' assert os.path.isfile(testgroupWebfingerFilename) assert 'acct:testgroup@' in open(testgroupWebfingerFilename).read() print('acct: exists within the webfinger endpoint for testgroup') testgroupHandle = 'testgroup@' + testgroupDomain followingStr = '' with open(aliceFollowingFilename, 'r') as fp: followingStr = fp.read() print('Alice following.txt:\n\n' + followingStr) if '!testgroup' not in followingStr: print('Alice following.txt does not contain !testgroup@' + testgroupDomain + ':' + str(testgroupPort)) assert is_group_actor(aliceDir, testgroupActor, alicePersonCache) assert not is_group_account(aliceDir, 'alice', aliceDomain) assert is_group_account(testgroupDir, 'testgroup', testgroupDomain) assert '!testgroup' in followingStr assert testgroupHandle in open(aliceFollowingFilename).read() assert testgroupHandle in open(aliceFollowingCalendarFilename).read() print('\n\n*********************************************************') print('Alice follows the test group') print('*********************************************************') print('Bob sends a follow request to the test group') os.chdir(bobDir) sessionBob = create_session(proxy_type) inReplyTo = None inReplyToAtomUri = None subject = None bobPostLog = [] followersOnly = False saveToFile = True client_to_server = False ccUrl = None bobPersonCache = {} bobCachedWebfingers = {} bobPostLog = [] # bobActor = http_prefix + '://' + bobAddress + '/users/bob' testgroupActor = \ http_prefix + '://' + testgroupAddress + '/users/testgroup' signing_priv_key_pem = None sendResult = \ send_follow_request(sessionBob, bobDir, 'bob', bobDomain, bobPort, http_prefix, 'testgroup', testgroupDomain, testgroupActor, testgroupPort, http_prefix, client_to_server, federation_list, bobSendThreads, bobPostLog, bobCachedWebfingers, bobPersonCache, True, __version__, signing_priv_key_pem) print('sendResult: ' + str(sendResult)) bobFollowingFilename = \ bobDir + '/accounts/bob@' + bobDomain + '/following.txt' bobFollowingCalendarFilename = \ bobDir + '/accounts/bob@' + bobDomain + \ '/followingCalendar.txt' testgroupFollowersFilename = \ testgroupDir + '/accounts/testgroup@' + testgroupDomain + \ '/followers.txt' for t in range(16): if os.path.isfile(testgroupFollowersFilename): if os.path.isfile(bobFollowingFilename): if os.path.isfile(bobFollowingCalendarFilename): break time.sleep(1) assert valid_inbox(testgroupDir, 'testgroup', testgroupDomain) assert valid_inbox_filenames(testgroupDir, 'testgroup', testgroupDomain, bobDomain, bobPort) assert 'bob@' + bobDomain in open(testgroupFollowersFilename).read() assert '!bob@' + bobDomain not in open(testgroupFollowersFilename).read() testgroupWebfingerFilename = \ testgroupDir + '/wfendpoints/testgroup@' + \ testgroupDomain + ':' + str(testgroupPort) + '.json' assert os.path.isfile(testgroupWebfingerFilename) assert 'acct:testgroup@' in open(testgroupWebfingerFilename).read() print('acct: exists within the webfinger endpoint for testgroup') testgroupHandle = 'testgroup@' + testgroupDomain followingStr = '' with open(bobFollowingFilename, 'r') as fp: followingStr = fp.read() print('Bob following.txt:\n\n' + followingStr) if '!testgroup' not in followingStr: print('Bob following.txt does not contain !testgroup@' + testgroupDomain + ':' + str(testgroupPort)) assert is_group_actor(bobDir, testgroupActor, bobPersonCache) assert '!testgroup' in followingStr assert testgroupHandle in open(bobFollowingFilename).read() assert testgroupHandle in open(bobFollowingCalendarFilename).read() print('Bob follows the test group') print('\n\n*********************************************************') print('Alice posts to the test group') inboxPathBob = \ bobDir + '/accounts/bob@' + bobDomain + '/inbox' startPostsBob = \ len([name for name in os.listdir(inboxPathBob) if os.path.isfile(os.path.join(inboxPathBob, name))]) assert startPostsBob == 0 alicePostLog = [] alicePersonCache = {} aliceCachedWebfingers = {} aliceSharedItemsFederatedDomains = [] aliceSharedItemFederationTokens = {} alicePostLog = [] isArticle = False city = 'London, England' low_bandwidth = False signing_priv_key_pem = None queuePath = \ testgroupDir + '/accounts/testgroup@' + testgroupDomain + '/queue' inboxPath = \ testgroupDir + '/accounts/testgroup@' + testgroupDomain + '/inbox' outboxPath = \ testgroupDir + '/accounts/testgroup@' + testgroupDomain + '/outbox' aliceMessageArrived = False startPostsInbox = \ len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) startPostsOutbox = \ len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) sendResult = \ send_post(signing_priv_key_pem, __version__, sessionAlice, aliceDir, 'alice', aliceDomain, alicePort, 'testgroup', testgroupDomain, testgroupPort, ccUrl, http_prefix, "Alice group message", followersOnly, saveToFile, client_to_server, True, None, None, None, city, federation_list, aliceSendThreads, alicePostLog, aliceCachedWebfingers, alicePersonCache, isArticle, system_language, aliceSharedItemsFederatedDomains, aliceSharedItemFederationTokens, low_bandwidth, content_license_url, inReplyTo, inReplyToAtomUri, subject) print('sendResult: ' + str(sendResult)) for i in range(20): time.sleep(1) if os.path.isdir(inboxPath): currPostsInbox = \ len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) currPostsOutbox = \ len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) if currPostsInbox > startPostsInbox and \ currPostsOutbox > startPostsOutbox: aliceMessageArrived = True print('Alice post sent to test group!') break assert aliceMessageArrived is True print('\n\n*********************************************************') print('Post from Alice to test group succeeded') print('\n\n*********************************************************') print('Check that post was relayed from test group to bob') bobMessageArrived = False for i in range(20): time.sleep(1) if os.path.isdir(inboxPathBob): currPostsBob = \ len([name for name in os.listdir(inboxPathBob) if os.path.isfile(os.path.join(inboxPathBob, name))]) if currPostsBob > startPostsBob: bobMessageArrived = True print('Bob received relayed group post!') break assert bobMessageArrived is True # check that the received post has an id from the group, # not from the original sender (alice) groupIdChecked = False for name in os.listdir(inboxPathBob): filename = os.path.join(inboxPathBob, name) if os.path.isfile(filename): receivedJson = load_json(filename) assert receivedJson print('Received group post ' + receivedJson['id']) assert '/testgroup/statuses/' in receivedJson['id'] groupIdChecked = True break assert groupIdChecked # stop the servers thrAlice.kill() thrAlice.join() assert thrAlice.is_alive() is False thrBob.kill() thrBob.join() assert thrBob.is_alive() is False thrGroup.kill() thrGroup.join() assert thrGroup.is_alive() is False # queue item removed time.sleep(4) assert len([name for name in os.listdir(queuePath) if os.path.isfile(os.path.join(queuePath, name))]) == 0 os.chdir(base_dir) shutil.rmtree(base_dir + '/.tests', ignore_errors=False, onerror=None) print('Testing following of a group is complete') def _test_followers_of_person(base_dir: str) -> None: print('testFollowersOfPerson') currDir = base_dir nickname = 'mxpop' domain = 'diva.domain' password = 'birb' port = 80 http_prefix = 'https' federation_list = [] base_dir = currDir + '/.tests_followersofperson' if os.path.isdir(base_dir): shutil.rmtree(base_dir, ignore_errors=False, onerror=None) os.mkdir(base_dir) os.chdir(base_dir) create_person(base_dir, nickname, domain, port, http_prefix, True, False, password) create_person(base_dir, 'maxboardroom', domain, port, http_prefix, True, False, password) create_person(base_dir, 'ultrapancake', domain, port, http_prefix, True, False, password) create_person(base_dir, 'drokk', domain, port, http_prefix, True, False, password) create_person(base_dir, 'sausagedog', domain, port, http_prefix, True, False, password) clear_follows(base_dir, nickname, domain) follow_person(base_dir, nickname, domain, 'maxboardroom', domain, federation_list, False, False) follow_person(base_dir, 'drokk', domain, 'ultrapancake', domain, federation_list, False, False) # deliberate duplication follow_person(base_dir, 'drokk', domain, 'ultrapancake', domain, federation_list, False, False) follow_person(base_dir, 'sausagedog', domain, 'ultrapancake', domain, federation_list, False, False) follow_person(base_dir, nickname, domain, 'ultrapancake', domain, federation_list, False, False) follow_person(base_dir, nickname, domain, 'someother', 'randodomain.net', federation_list, False, False) followList = get_followers_of_person(base_dir, 'ultrapancake', domain) assert len(followList) == 3 assert 'mxpop@' + domain in followList assert 'drokk@' + domain in followList assert 'sausagedog@' + domain in followList os.chdir(currDir) shutil.rmtree(base_dir, ignore_errors=False, onerror=None) def _test_followers_on_domain(base_dir: str) -> None: print('testNoOfFollowersOnDomain') currDir = base_dir nickname = 'mxpop' domain = 'diva.domain' otherdomain = 'soup.dragon' password = 'birb' port = 80 http_prefix = 'https' federation_list = [] base_dir = currDir + '/.tests_nooffollowersOndomain' if os.path.isdir(base_dir): shutil.rmtree(base_dir, ignore_errors=False, onerror=None) os.mkdir(base_dir) os.chdir(base_dir) create_person(base_dir, nickname, domain, port, http_prefix, True, False, password) create_person(base_dir, 'maxboardroom', otherdomain, port, http_prefix, True, False, password) create_person(base_dir, 'ultrapancake', otherdomain, port, http_prefix, True, False, password) create_person(base_dir, 'drokk', otherdomain, port, http_prefix, True, False, password) create_person(base_dir, 'sausagedog', otherdomain, port, http_prefix, True, False, password) follow_person(base_dir, 'drokk', otherdomain, nickname, domain, federation_list, False, False) follow_person(base_dir, 'sausagedog', otherdomain, nickname, domain, federation_list, False, False) follow_person(base_dir, 'maxboardroom', otherdomain, nickname, domain, federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'cucumber', 'sandwiches.party', federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'captainsensible', 'damned.zone', federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'pilchard', 'zombies.attack', federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'drokk', otherdomain, federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'sausagedog', otherdomain, federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'maxboardroom', otherdomain, federation_list, False, False) followersOnOtherDomain = \ no_of_followers_on_domain(base_dir, nickname + '@' + domain, otherdomain) assert followersOnOtherDomain == 3 unfollower_of_account(base_dir, nickname, domain, 'sausagedog', otherdomain, False, False) followersOnOtherDomain = \ no_of_followers_on_domain(base_dir, nickname + '@' + domain, otherdomain) assert followersOnOtherDomain == 2 os.chdir(currDir) shutil.rmtree(base_dir, ignore_errors=False, onerror=None) def _test_group_followers(base_dir: str) -> None: print('test_group_followers') currDir = base_dir nickname = 'test735' domain = 'mydomain.com' password = 'somepass' port = 80 http_prefix = 'https' federation_list = [] base_dir = currDir + '/.tests_testgroupfollowers' if os.path.isdir(base_dir): shutil.rmtree(base_dir, ignore_errors=False, onerror=None) os.mkdir(base_dir) os.chdir(base_dir) create_person(base_dir, nickname, domain, port, http_prefix, True, False, password) clear_followers(base_dir, nickname, domain) add_follower_of_person(base_dir, nickname, domain, 'badger', 'wild.domain', federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'squirrel', 'wild.domain', federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'rodent', 'wild.domain', federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'utterly', 'clutterly.domain', federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'zonked', 'zzz.domain', federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'nap', 'zzz.domain', federation_list, False, False) grouped = group_followers_by_domain(base_dir, nickname, domain) assert len(grouped.items()) == 3 assert grouped.get('zzz.domain') assert grouped.get('clutterly.domain') assert grouped.get('wild.domain') assert len(grouped['zzz.domain']) == 2 assert len(grouped['wild.domain']) == 3 assert len(grouped['clutterly.domain']) == 1 os.chdir(currDir) shutil.rmtree(base_dir, ignore_errors=False, onerror=None) def _test_follows(base_dir: str) -> None: print('testFollows') currDir = base_dir nickname = 'test529' domain = 'testdomain.com' password = 'mypass' port = 80 http_prefix = 'https' federation_list = ['wild.com', 'mesh.com'] base_dir = currDir + '/.tests_testfollows' if os.path.isdir(base_dir): shutil.rmtree(base_dir, ignore_errors=False, onerror=None) os.mkdir(base_dir) os.chdir(base_dir) create_person(base_dir, nickname, domain, port, http_prefix, True, False, password) clear_follows(base_dir, nickname, domain) follow_person(base_dir, nickname, domain, 'badger', 'wild.com', federation_list, False, False) follow_person(base_dir, nickname, domain, 'squirrel', 'secret.com', federation_list, False, False) follow_person(base_dir, nickname, domain, 'rodent', 'drainpipe.com', federation_list, False, False) follow_person(base_dir, nickname, domain, 'batman', 'mesh.com', federation_list, False, False) follow_person(base_dir, nickname, domain, 'giraffe', 'trees.com', federation_list, False, False) accountDir = acct_dir(base_dir, nickname, domain) f = open(accountDir + '/following.txt', 'r') domainFound = False for followingDomain in f: testDomain = followingDomain.split('@')[1] testDomain = testDomain.replace('\n', '').replace('\r', '') if testDomain == 'mesh.com': domainFound = True if testDomain not in federation_list: print(testDomain) assert(False) assert(domainFound) unfollow_account(base_dir, nickname, domain, 'batman', 'mesh.com', True, False) domainFound = False for followingDomain in f: testDomain = followingDomain.split('@')[1] testDomain = testDomain.replace('\n', '').replace('\r', '') if testDomain == 'mesh.com': domainFound = True assert(domainFound is False) clear_followers(base_dir, nickname, domain) add_follower_of_person(base_dir, nickname, domain, 'badger', 'wild.com', federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'squirrel', 'secret.com', federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'rodent', 'drainpipe.com', federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'batman', 'mesh.com', federation_list, False, False) add_follower_of_person(base_dir, nickname, domain, 'giraffe', 'trees.com', federation_list, False, False) accountDir = acct_dir(base_dir, nickname, domain) f = open(accountDir + '/followers.txt', 'r') for followerDomain in f: testDomain = followerDomain.split('@')[1] testDomain = testDomain.replace('\n', '').replace('\r', '') if testDomain not in federation_list: print(testDomain) assert(False) os.chdir(currDir) shutil.rmtree(base_dir, ignore_errors=False, onerror=None) def _test_create_person_account(base_dir: str): print('testCreatePerson') system_language = 'en' currDir = base_dir nickname = 'test382' domain = 'badgerdomain.com' password = 'mypass' port = 80 http_prefix = 'https' client_to_server = False base_dir = currDir + '/.tests_createperson' if os.path.isdir(base_dir): shutil.rmtree(base_dir, ignore_errors=False, onerror=None) os.mkdir(base_dir) os.chdir(base_dir) privateKeyPem, publicKeyPem, person, wfEndpoint = \ create_person(base_dir, nickname, domain, port, http_prefix, True, False, password) assert os.path.isfile(base_dir + '/accounts/passwords') delete_all_posts(base_dir, nickname, domain, 'inbox') delete_all_posts(base_dir, nickname, domain, 'outbox') set_display_nickname(base_dir, nickname, domain, 'badger') set_bio(base_dir, nickname, domain, 'Randomly roaming in your backyard') archive_posts_for_person(nickname, domain, base_dir, 'inbox', None, {}, 4) archive_posts_for_person(nickname, domain, base_dir, 'outbox', None, {}, 4) testInReplyTo = None testInReplyToAtomUri = None testSubject = None testSchedulePost = False testEventDate = None testEventTime = None testLocation = None testIsArticle = False content = "G'day world!" followersOnly = False saveToFile = True commentsEnabled = True attachImageFilename = None mediaType = None conversationId = None low_bandwidth = True content_license_url = 'https://creativecommons.org/licenses/by/4.0' create_public_post(base_dir, nickname, domain, port, http_prefix, content, followersOnly, saveToFile, client_to_server, commentsEnabled, attachImageFilename, mediaType, 'Not suitable for Vogons', 'London, England', testInReplyTo, testInReplyToAtomUri, testSubject, testSchedulePost, testEventDate, testEventTime, testLocation, testIsArticle, system_language, conversationId, low_bandwidth, content_license_url) os.chdir(currDir) shutil.rmtree(base_dir, ignore_errors=False, onerror=None) def show_test_boxes(name: str, inboxPath: str, outboxPath: str) -> None: inboxPosts = \ len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) outboxPosts = \ len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) print('EVENT: ' + name + ' inbox has ' + str(inboxPosts) + ' posts and ' + str(outboxPosts) + ' outbox posts') def _test_authentication(base_dir: str) -> None: print('testAuthentication') currDir = base_dir nickname = 'test8743' password = 'SuperSecretPassword12345' base_dir = currDir + '/.tests_authentication' if os.path.isdir(base_dir): shutil.rmtree(base_dir, ignore_errors=False, onerror=None) os.mkdir(base_dir) os.chdir(base_dir) assert store_basic_credentials(base_dir, 'othernick', 'otherpass') assert store_basic_credentials(base_dir, 'bad:nick', 'otherpass') is False assert store_basic_credentials(base_dir, 'badnick', 'otherpa:ss') is False assert store_basic_credentials(base_dir, nickname, password) authHeader = create_basic_auth_header(nickname, password) assert authorize_basic(base_dir, '/users/' + nickname + '/inbox', authHeader, False) assert authorize_basic(base_dir, '/users/' + nickname, authHeader, False) is False assert authorize_basic(base_dir, '/users/othernick/inbox', authHeader, False) is False authHeader = create_basic_auth_header(nickname, password + '1') assert authorize_basic(base_dir, '/users/' + nickname + '/inbox', authHeader, False) is False password = 'someOtherPassword' assert store_basic_credentials(base_dir, nickname, password) authHeader = create_basic_auth_header(nickname, password) assert authorize_basic(base_dir, '/users/' + nickname + '/inbox', authHeader, False) os.chdir(currDir) shutil.rmtree(base_dir, ignore_errors=False, onerror=None) def test_client_to_server(base_dir: str): print('EVENT: Testing sending a post via c2s') global testServerAliceRunning global testServerBobRunning content_license_url = 'https://creativecommons.org/licenses/by/4.0' testServerAliceRunning = False testServerBobRunning = False system_language = 'en' http_prefix = 'http' proxy_type = None federation_list = [] low_bandwidth = False if os.path.isdir(base_dir + '/.tests'): shutil.rmtree(base_dir + '/.tests', ignore_errors=False, onerror=None) os.mkdir(base_dir + '/.tests') # create the servers aliceDir = base_dir + '/.tests/alice' aliceDomain = '127.0.0.42' alicePort = 61935 aliceSendThreads = [] aliceAddress = aliceDomain + ':' + str(alicePort) bobDir = base_dir + '/.tests/bob' bobDomain = '127.0.0.64' bobPort = 61936 bobSendThreads = [] bobAddress = bobDomain + ':' + str(bobPort) global thrAlice if thrAlice: while thrAlice.is_alive(): thrAlice.stop() time.sleep(1) thrAlice.kill() thrAlice = \ thread_with_trace(target=create_server_alice, args=(aliceDir, aliceDomain, alicePort, bobAddress, federation_list, False, False, aliceSendThreads), daemon=True) global thrBob if thrBob: while thrBob.is_alive(): thrBob.stop() time.sleep(1) thrBob.kill() thrBob = \ thread_with_trace(target=create_server_bob, args=(bobDir, bobDomain, bobPort, aliceAddress, federation_list, False, False, bobSendThreads), daemon=True) thrAlice.start() thrBob.start() assert thrAlice.is_alive() is True assert thrBob.is_alive() is True # wait for both servers to be running ctr = 0 while not (testServerAliceRunning and testServerBobRunning): time.sleep(1) ctr += 1 if ctr > 60: break print('Alice online: ' + str(testServerAliceRunning)) print('Bob online: ' + str(testServerBobRunning)) time.sleep(1) print('\n\n*******************************************************') print('EVENT: Alice sends to Bob via c2s') sessionAlice = create_session(proxy_type) followersOnly = False attachedImageFilename = base_dir + '/img/logo.png' mediaType = get_attachment_media_type(attachedImageFilename) attachedImageDescription = 'Logo' city = 'London, England' isArticle = False cached_webfingers = {} person_cache = {} password = 'alicepass' conversationId = None aliceInboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/inbox' aliceOutboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/outbox' bobInboxPath = bobDir + '/accounts/bob@' + bobDomain + '/inbox' bobOutboxPath = bobDir + '/accounts/bob@' + bobDomain + '/outbox' outboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/outbox' inboxPath = bobDir + '/accounts/bob@' + bobDomain + '/inbox' show_test_boxes('alice', aliceInboxPath, aliceOutboxPath) show_test_boxes('bob', bobInboxPath, bobOutboxPath) assert len([name for name in os.listdir(aliceInboxPath) if os.path.isfile(os.path.join(aliceInboxPath, name))]) == 0 assert len([name for name in os.listdir(aliceOutboxPath) if os.path.isfile(os.path.join(aliceOutboxPath, name))]) == 0 assert len([name for name in os.listdir(bobInboxPath) if os.path.isfile(os.path.join(bobInboxPath, name))]) == 0 assert len([name for name in os.listdir(bobOutboxPath) if os.path.isfile(os.path.join(bobOutboxPath, name))]) == 0 print('EVENT: all inboxes and outboxes are empty') signing_priv_key_pem = None sendResult = \ send_post_via_server(signing_priv_key_pem, __version__, aliceDir, sessionAlice, 'alice', password, aliceDomain, alicePort, 'bob', bobDomain, bobPort, None, http_prefix, 'Sent from my ActivityPub client', followersOnly, True, attachedImageFilename, mediaType, attachedImageDescription, city, cached_webfingers, person_cache, isArticle, system_language, low_bandwidth, content_license_url, True, None, None, conversationId, None) print('sendResult: ' + str(sendResult)) for i in range(30): if os.path.isdir(outboxPath): if len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) == 1: break time.sleep(1) show_test_boxes('alice', aliceInboxPath, aliceOutboxPath) show_test_boxes('bob', bobInboxPath, bobOutboxPath) assert len([name for name in os.listdir(aliceInboxPath) if os.path.isfile(os.path.join(aliceInboxPath, name))]) == 0 assert len([name for name in os.listdir(aliceOutboxPath) if os.path.isfile(os.path.join(aliceOutboxPath, name))]) == 1 print(">>> c2s post arrived in Alice's outbox\n\n\n") for i in range(30): if os.path.isdir(inboxPath): if len([name for name in os.listdir(bobInboxPath) if os.path.isfile(os.path.join(bobInboxPath, name))]) == 1: break time.sleep(1) show_test_boxes('alice', aliceInboxPath, aliceOutboxPath) show_test_boxes('bob', bobInboxPath, bobOutboxPath) assert len([name for name in os.listdir(bobInboxPath) if os.path.isfile(os.path.join(bobInboxPath, name))]) == 1 assert len([name for name in os.listdir(bobOutboxPath) if os.path.isfile(os.path.join(bobOutboxPath, name))]) == 0 print(">>> s2s post arrived in Bob's inbox") print("c2s send success\n\n\n") print('\n\nEVENT: Getting message id for the post') statusNumber = 0 outboxPostFilename = None outboxPostId = None for name in os.listdir(outboxPath): if '#statuses#' in name: statusNumber = name.split('#statuses#')[1].replace('.json', '') statusNumber = int(statusNumber.replace('#activity', '')) outboxPostFilename = outboxPath + '/' + name post_json_object = load_json(outboxPostFilename, 0) if post_json_object: outboxPostId = remove_id_ending(post_json_object['id']) assert outboxPostId print('message id obtained: ' + outboxPostId) assert valid_inbox(bobDir, 'bob', bobDomain) assert valid_inbox_filenames(bobDir, 'bob', bobDomain, aliceDomain, alicePort) print('\n\nAlice follows Bob') signing_priv_key_pem = None send_follow_requestViaServer(aliceDir, sessionAlice, 'alice', password, aliceDomain, alicePort, 'bob', bobDomain, bobPort, http_prefix, cached_webfingers, person_cache, True, __version__, signing_priv_key_pem) alicePetnamesFilename = aliceDir + '/accounts/' + \ 'alice@' + aliceDomain + '/petnames.txt' aliceFollowingFilename = \ aliceDir + '/accounts/alice@' + aliceDomain + '/following.txt' bobFollowersFilename = \ bobDir + '/accounts/bob@' + bobDomain + '/followers.txt' for t in range(10): if os.path.isfile(bobFollowersFilename): if 'alice@' + aliceDomain + ':' + str(alicePort) in \ open(bobFollowersFilename).read(): if os.path.isfile(aliceFollowingFilename) and \ os.path.isfile(alicePetnamesFilename): if 'bob@' + bobDomain + ':' + str(bobPort) in \ open(aliceFollowingFilename).read(): break time.sleep(1) assert os.path.isfile(bobFollowersFilename) assert os.path.isfile(aliceFollowingFilename) assert os.path.isfile(alicePetnamesFilename) assert 'bob bob@' + bobDomain in \ open(alicePetnamesFilename).read() print('alice@' + aliceDomain + ':' + str(alicePort) + ' in ' + bobFollowersFilename) assert 'alice@' + aliceDomain + ':' + str(alicePort) in \ open(bobFollowersFilename).read() print('bob@' + bobDomain + ':' + str(bobPort) + ' in ' + aliceFollowingFilename) assert 'bob@' + bobDomain + ':' + str(bobPort) in \ open(aliceFollowingFilename).read() assert valid_inbox(bobDir, 'bob', bobDomain) assert valid_inbox_filenames(bobDir, 'bob', bobDomain, aliceDomain, alicePort) print('\n\nEVENT: Bob follows Alice') send_follow_requestViaServer(aliceDir, sessionAlice, 'bob', 'bobpass', bobDomain, bobPort, 'alice', aliceDomain, alicePort, http_prefix, cached_webfingers, person_cache, True, __version__, signing_priv_key_pem) for t in range(10): if os.path.isfile(aliceDir + '/accounts/alice@' + aliceDomain + '/followers.txt'): if 'bob@' + bobDomain + ':' + str(bobPort) in \ open(aliceDir + '/accounts/alice@' + aliceDomain + '/followers.txt').read(): if os.path.isfile(bobDir + '/accounts/bob@' + bobDomain + '/following.txt'): aliceHandleStr = \ 'alice@' + aliceDomain + ':' + str(alicePort) if aliceHandleStr in \ open(bobDir + '/accounts/bob@' + bobDomain + '/following.txt').read(): if os.path.isfile(bobDir + '/accounts/bob@' + bobDomain + '/followingCalendar.txt'): if aliceHandleStr in \ open(bobDir + '/accounts/bob@' + bobDomain + '/followingCalendar.txt').read(): break time.sleep(1) assert os.path.isfile(aliceDir + '/accounts/alice@' + aliceDomain + '/followers.txt') assert os.path.isfile(bobDir + '/accounts/bob@' + bobDomain + '/following.txt') assert 'bob@' + bobDomain + ':' + str(bobPort) in \ open(aliceDir + '/accounts/alice@' + aliceDomain + '/followers.txt').read() assert 'alice@' + aliceDomain + ':' + str(alicePort) in \ open(bobDir + '/accounts/bob@' + bobDomain + '/following.txt').read() sessionBob = create_session(proxy_type) password = 'bobpass' outboxPath = bobDir + '/accounts/bob@' + bobDomain + '/outbox' inboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/inbox' print(str(len([name for name in os.listdir(bobOutboxPath) if os.path.isfile(os.path.join(bobOutboxPath, name))]))) show_test_boxes('alice', aliceInboxPath, aliceOutboxPath) show_test_boxes('bob', bobInboxPath, bobOutboxPath) assert len([name for name in os.listdir(bobOutboxPath) if os.path.isfile(os.path.join(bobOutboxPath, name))]) == 1 print(str(len([name for name in os.listdir(aliceInboxPath) if os.path.isfile(os.path.join(aliceInboxPath, name))]))) show_test_boxes('alice', aliceInboxPath, aliceOutboxPath) show_test_boxes('bob', bobInboxPath, bobOutboxPath) assert len([name for name in os.listdir(aliceInboxPath) if os.path.isfile(os.path.join(aliceInboxPath, name))]) == 0 print('\n\nEVENT: Bob likes the post') send_like_via_server(bobDir, sessionBob, 'bob', 'bobpass', bobDomain, bobPort, http_prefix, outboxPostId, cached_webfingers, person_cache, True, __version__, signing_priv_key_pem) for i in range(20): if os.path.isdir(outboxPath) and os.path.isdir(inboxPath): if len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) == 2: test = len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) if test == 1: break time.sleep(1) show_test_boxes('alice', aliceInboxPath, aliceOutboxPath) show_test_boxes('bob', bobInboxPath, bobOutboxPath) bobOutboxPathCtr = \ len([name for name in os.listdir(bobOutboxPath) if os.path.isfile(os.path.join(bobOutboxPath, name))]) print('bobOutboxPathCtr: ' + str(bobOutboxPathCtr)) assert bobOutboxPathCtr == 2 aliceInboxPathCtr = \ len([name for name in os.listdir(aliceInboxPath) if os.path.isfile(os.path.join(aliceInboxPath, name))]) print('aliceInboxPathCtr: ' + str(aliceInboxPathCtr)) assert aliceInboxPathCtr == 0 print('EVENT: Post liked') print('\n\nEVENT: Bob reacts to the post') send_reaction_via_server(bobDir, sessionBob, 'bob', 'bobpass', bobDomain, bobPort, http_prefix, outboxPostId, '😃', cached_webfingers, person_cache, True, __version__, signing_priv_key_pem) for i in range(20): if os.path.isdir(outboxPath) and os.path.isdir(inboxPath): if len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) == 3: test = len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) if test == 1: break time.sleep(1) show_test_boxes('alice', aliceInboxPath, aliceOutboxPath) show_test_boxes('bob', bobInboxPath, bobOutboxPath) bobOutboxPathCtr = \ len([name for name in os.listdir(bobOutboxPath) if os.path.isfile(os.path.join(bobOutboxPath, name))]) print('bobOutboxPathCtr: ' + str(bobOutboxPathCtr)) assert bobOutboxPathCtr == 3 aliceInboxPathCtr = \ len([name for name in os.listdir(aliceInboxPath) if os.path.isfile(os.path.join(aliceInboxPath, name))]) print('aliceInboxPathCtr: ' + str(aliceInboxPathCtr)) assert aliceInboxPathCtr == 0 print('EVENT: Post reacted to') print(str(len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]))) show_test_boxes('alice', aliceInboxPath, aliceOutboxPath) show_test_boxes('bob', bobInboxPath, bobOutboxPath) outboxPathCtr = \ len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) print('outboxPathCtr: ' + str(outboxPathCtr)) assert outboxPathCtr == 3 inboxPathCtr = \ len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) print('inboxPathCtr: ' + str(inboxPathCtr)) assert inboxPathCtr == 0 show_test_boxes('alice', aliceInboxPath, aliceOutboxPath) show_test_boxes('bob', bobInboxPath, bobOutboxPath) print('\n\nEVENT: Bob repeats the post') signing_priv_key_pem = None send_announce_via_server(bobDir, sessionBob, 'bob', password, bobDomain, bobPort, http_prefix, outboxPostId, cached_webfingers, person_cache, True, __version__, signing_priv_key_pem) for i in range(20): if os.path.isdir(outboxPath) and os.path.isdir(inboxPath): if len([name for name in os.listdir(outboxPath) if os.path.isfile(os.path.join(outboxPath, name))]) == 4: if len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) == 2: break time.sleep(1) show_test_boxes('alice', aliceInboxPath, aliceOutboxPath) show_test_boxes('bob', bobInboxPath, bobOutboxPath) bobOutboxPathCtr = \ len([name for name in os.listdir(bobOutboxPath) if os.path.isfile(os.path.join(bobOutboxPath, name))]) print('bobOutboxPathCtr: ' + str(bobOutboxPathCtr)) assert bobOutboxPathCtr == 5 aliceInboxPathCtr = \ len([name for name in os.listdir(aliceInboxPath) if os.path.isfile(os.path.join(aliceInboxPath, name))]) print('aliceInboxPathCtr: ' + str(aliceInboxPathCtr)) assert aliceInboxPathCtr == 1 print('EVENT: Post repeated') inboxPath = bobDir + '/accounts/bob@' + bobDomain + '/inbox' outboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/outbox' postsBefore = \ len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) print('\n\nEVENT: Alice deletes her post: ' + outboxPostId + ' ' + str(postsBefore)) password = 'alicepass' send_delete_via_server(aliceDir, sessionAlice, 'alice', password, aliceDomain, alicePort, http_prefix, outboxPostId, cached_webfingers, person_cache, True, __version__, signing_priv_key_pem) for i in range(30): if os.path.isdir(inboxPath): test = len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) if test == postsBefore-1: break time.sleep(1) test = len([name for name in os.listdir(inboxPath) if os.path.isfile(os.path.join(inboxPath, name))]) assert test == postsBefore - 1 print(">>> post deleted from Alice's outbox and Bob's inbox") assert valid_inbox(bobDir, 'bob', bobDomain) assert valid_inbox_filenames(bobDir, 'bob', bobDomain, aliceDomain, alicePort) print('\n\nEVENT: Alice unfollows Bob') password = 'alicepass' send_unfollow_request_via_server(base_dir, sessionAlice, 'alice', password, aliceDomain, alicePort, 'bob', bobDomain, bobPort, http_prefix, cached_webfingers, person_cache, True, __version__, signing_priv_key_pem) for t in range(10): if 'alice@' + aliceDomain + ':' + str(alicePort) not in \ open(bobFollowersFilename).read(): if 'bob@' + bobDomain + ':' + str(bobPort) not in \ open(aliceFollowingFilename).read(): break time.sleep(1) assert os.path.isfile(bobFollowersFilename) assert os.path.isfile(aliceFollowingFilename) assert 'alice@' + aliceDomain + ':' + str(alicePort) \ not in open(bobFollowersFilename).read() assert 'bob@' + bobDomain + ':' + str(bobPort) \ not in open(aliceFollowingFilename).read() assert valid_inbox(bobDir, 'bob', bobDomain) assert valid_inbox_filenames(bobDir, 'bob', bobDomain, aliceDomain, alicePort) assert valid_inbox(aliceDir, 'alice', aliceDomain) assert valid_inbox_filenames(aliceDir, 'alice', aliceDomain, bobDomain, bobPort) # stop the servers thrAlice.kill() thrAlice.join() assert thrAlice.is_alive() is False thrBob.kill() thrBob.join() assert thrBob.is_alive() is False os.chdir(base_dir) # shutil.rmtree(aliceDir, ignore_errors=False, onerror=None) # shutil.rmtree(bobDir, ignore_errors=False, onerror=None) def _test_actor_parsing(): print('testActorParsing') actor = 'https://mydomain:72/users/mynick' domain, port = get_domain_from_actor(actor) assert domain == 'mydomain' assert port == 72 nickname = get_nickname_from_actor(actor) assert nickname == 'mynick' actor = 'https://element/accounts/badger' domain, port = get_domain_from_actor(actor) assert domain == 'element' nickname = get_nickname_from_actor(actor) assert nickname == 'badger' actor = 'egg@chicken.com' domain, port = get_domain_from_actor(actor) assert domain == 'chicken.com' nickname = get_nickname_from_actor(actor) assert nickname == 'egg' actor = '@waffle@cardboard' domain, port = get_domain_from_actor(actor) assert domain == 'cardboard' nickname = get_nickname_from_actor(actor) assert nickname == 'waffle' actor = 'https://astral/channel/sky' domain, port = get_domain_from_actor(actor) assert domain == 'astral' nickname = get_nickname_from_actor(actor) assert nickname == 'sky' actor = 'https://randomain/users/rando' domain, port = get_domain_from_actor(actor) assert domain == 'randomain' nickname = get_nickname_from_actor(actor) assert nickname == 'rando' actor = 'https://otherdomain:49/@othernick' domain, port = get_domain_from_actor(actor) assert domain == 'otherdomain' assert port == 49 nickname = get_nickname_from_actor(actor) assert nickname == 'othernick' def _test_web_links(): print('testWebLinks') exampleText = \ "
Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + \ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + \ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + \ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + \ " #turbot #haddock
" resultText = remove_long_words(exampleText, 40, []) assert resultText == "Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + \ " #turbot " + \ "#haddock
" exampleText = \ '@foo Some ' + \ 'random text.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + \ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + \ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + \ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + \ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + \ 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
' resultText = remove_long_words(exampleText, 40, []) assert resultText == \ '@foo ' + \ 'Some random text.
' exampleText = \ 'This post has a web links https://somesite.net\n\nAnd some other text' linkedText = add_web_links(exampleText) assert \ 'https://' + \ 'somesite.net1. HAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAH' + \ 'AHAHAHHAHAHAHAHAHAHAHAHAHAHAHAHHAHAHAHAHAHAHAHAH' resultText = remove_long_words(exampleText, 40, []) assert resultText == '1. HAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHA
' exampleText = \ 'Tox address is 88AB9DED6F9FBEF43E105FB72060A2D89F9B93C74' + \ '4E8C45AB3C5E42C361C837155AFCFD9D448
' resultText = remove_long_words(exampleText, 40, []) assert resultText == exampleText exampleText = \ 'some.incredibly.long.and.annoying.word.which.should.be.removed: ' + \ 'The remaining text' resultText = remove_long_words(exampleText, 40, []) assert resultText == \ 'some.incredibly.long.and.annoying.word.w\n' + \ 'hich.should.be.removed: The remaining text' exampleText = \ 'Tox address is 88AB9DED6F9FBEF43E105FB72060A2D89F9B93C74' + \ '4E8C45AB3C5E42C361C837155AFCFD9D448
' resultText = remove_long_words(exampleText, 40, []) assert resultText == \ 'Tox address is 88AB9DED6F9FBEF43E105FB72060A2D89F9B93C7\n' + \ '44E8C45AB3C5E42C361C837155AFCFD9D448
' exampleText = \ 'ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCA' + \ 'BCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCAB' + \ 'CABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC' + \ 'ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCA' + \ 'BCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCAB' + \ 'CABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC' + \ 'ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCA' + \ 'BCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCAB' + \ 'CABCABCABCABCABCABCABCABC
' resultText = remove_long_words(exampleText, 40, []) assert resultText == r'ABCABCABCABCABCABCABCABCABCABCABCABCABCA<\p>' exampleText = \ '"the nucleus of mutual-support institutions, habits, and customs ' + \ 'remains alive with the millions; it keeps them together; and ' + \ 'they prefer to cling to their customs, beliefs, and traditions ' + \ 'rather than to accept the teachings of a war of each ' + \ 'against all"\n\n--Peter Kropotkin' testFnStr = add_web_links(exampleText) resultText = remove_long_words(testFnStr, 40, []) assert resultText == exampleText assert 'ellipsis' not in resultText exampleText = \ '
filepopout=' + \ 'TemplateAttachmentRichPopout<<\\p>' resultText = replace_content_duplicates(exampleText) assert resultText == \ '
filepopout=' + \ 'TemplateAttachmentRichPopout' exampleText = \ '
Test1 test2 #YetAnotherExcessivelyLongwindedAndBoringHashtag
' testFnStr = add_web_links(exampleText) resultText = remove_long_words(testFnStr, 40, []) assert(resultText == 'Test1 test2 ' '#YetAnotherExcessivelyLongwindedAndBorin\ngHashtag
') exampleText = \ "Don't remove a p2p link " + \ "rad:git:hwd1yrerc3mcgn8ga9rho3dqi4w33nep7kxmqezss4topyfgmexihp" + \ "33xcw
" testFnStr = add_web_links(exampleText) resultText = remove_long_words(testFnStr, 40, []) assert resultText == exampleText def _test_addemoji(base_dir: str): print('testAddEmoji') content = "Emoji :lemon: :strawberry: :banana:" http_prefix = 'http' nickname = 'testuser' domain = 'testdomain.net' port = 3682 recipients = [] hashtags = {} base_dirOriginal = base_dir path = base_dir + '/.tests' if not os.path.isdir(path): os.mkdir(path) path = base_dir + '/.tests/emoji' if os.path.isdir(path): shutil.rmtree(path, ignore_errors=False, onerror=None) os.mkdir(path) base_dir = path path = base_dir + '/emoji' if os.path.isdir(path): shutil.rmtree(path, ignore_errors=False, onerror=None) os.mkdir(path) copytree(base_dirOriginal + '/emoji', base_dir + '/emoji') os.chdir(base_dir) privateKeyPem, publicKeyPem, person, wfEndpoint = \ create_person(base_dir, nickname, domain, port, http_prefix, True, False, 'password') contentModified = \ add_html_tags(base_dir, http_prefix, nickname, domain, content, recipients, hashtags, True) assert ':lemon:' in contentModified assert contentModified.startswith('') assert contentModified.endswith('
') tags = [] for tagName, tag in hashtags.items(): tags.append(tag) content = contentModified contentModified = \ replace_emoji_from_tags(None, base_dir, content, tags, 'content', True) # print('contentModified: ' + contentModified) assert contentModified == 'Emoji 🍋 🍓 🍌
' os.chdir(base_dirOriginal) shutil.rmtree(base_dirOriginal + '/.tests', ignore_errors=False, onerror=None) def _test_get_status_number(): print('testGetStatusNumber') prevStatusNumber = None for i in range(1, 20): statusNumber, published = get_status_number() if prevStatusNumber: assert len(statusNumber) == 18 assert int(statusNumber) > prevStatusNumber prevStatusNumber = int(statusNumber) def _test_json_string() -> None: print('testJsonString') filename = '.epicyon_tests_test_json_string.json' messageStr = "Crème brûlée यह एक परीक्षण ह" testJson = { "content": messageStr } assert save_json(testJson, filename) receivedJson = load_json(filename, 0) assert receivedJson assert receivedJson['content'] == messageStr encodedStr = json.dumps(testJson, ensure_ascii=False) assert messageStr in encodedStr try: os.remove(filename) except OSError: pass def _test_save_load_json(): print('testSaveLoadJson') testJson = { "param1": 3, "param2": '"Crème brûlée यह एक परीक्षण ह"' } testFilename = '.epicyon_tests_test_save_load_json.json' if os.path.isfile(testFilename): try: os.remove(testFilename) except OSError: pass assert save_json(testJson, testFilename) assert os.path.isfile(testFilename) testLoadJson = load_json(testFilename) assert(testLoadJson) assert testLoadJson.get('param1') assert testLoadJson.get('param2') assert testLoadJson['param1'] == 3 assert testLoadJson['param2'] == '"Crème brûlée यह एक परीक्षण ह"' try: os.remove(testFilename) except OSError: pass def _test_theme(): print('testTheme') css = 'somestring --background-value: 24px; --foreground-value: 24px;' result = set_cs_sparam(css, 'background-value', '32px') assert result == \ 'somestring --background-value: 32px; --foreground-value: 24px;' css = \ 'somestring --background-value: 24px; --foreground-value: 24px; ' + \ '--background-value: 24px;' result = set_cs_sparam(css, 'background-value', '32px') assert result == \ 'somestring --background-value: 32px; --foreground-value: 24px; ' + \ '--background-value: 32px;' css = '--background-value: 24px; --foreground-value: 24px;' result = set_cs_sparam(css, 'background-value', '32px') assert result == '--background-value: 32px; --foreground-value: 24px;' def _test_recent_posts_cache(): print('testRecentPostsCache') recent_posts_cache = {} max_recent_posts = 3 htmlStr = '' for i in range(5): post_json_object = { "id": "https://somesite.whatever/users/someuser/statuses/" + str(i) } update_recent_posts_cache(recent_posts_cache, max_recent_posts, post_json_object, htmlStr) assert len(recent_posts_cache['index']) == max_recent_posts assert len(recent_posts_cache['json'].items()) == max_recent_posts assert len(recent_posts_cache['html'].items()) == max_recent_posts def _test_remove_txt_formatting(): print('testRemoveTextFormatting') testStr = 'Text without formatting
' resultStr = remove_text_formatting(testStr) assert(resultStr == testStr) testStr = 'Text with
Text with formatting
') def _test_jsonld(): print("testJsonld") jldDocument = { "@context": "https://www.w3.org/ns/activitystreams", "actor": "https://somesite.net/users/gerbil", "description": "My json document", "numberField": 83582, "object": { "content": "valid content" } } # privateKeyPem, publicKeyPem = generate_rsa_key() privateKeyPem = '-----BEGIN RSA PRIVATE KEY-----\n' \ 'MIIEowIBAAKCAQEAod9iHfIn4ugY/2byFrFjUprrFLkkH5bCrjiBq2/MdHFg99IQ\n' \ '7li2x2mg5fkBMhU5SJIxlN8kiZMFq7JUXSA97Yo4puhVubqTSHihIh6Xn2mTjTgs\n' \ 'zNo9SBbmN3YiyBPTcr0rF4jGWZAduJ8u6i7Eky2QH+UBKyUNRZrcfoVq+7grHUIA\n' \ '45pE7vAfEEWtgRiw32Nwlx55N3hayHax0y8gMdKEF/vfYKRLcM7rZgEASMtlCpgy\n' \ 'fsyHwFCDzl/BP8AhP9u3dM+SEundeAvF58AiXx1pKvBpxqttDNAsKWCRQ06/WI/W\n' \ '2Rwihl9yCjobqRoFsZ/cTEi6FG9AbDAds5YjTwIDAQABAoIBAERL3rbpy8Bl0t43\n' \ 'jh7a+yAIMvVMZBxb3InrV3KAug/LInGNFQ2rKnsaawN8uu9pmwCuhfLc7yqIeJUH\n' \ 'qaadCuPlNJ/fWQQC309tbfbaV3iv78xejjBkSATZfIqb8nLeQpGflMXaNG3na1LQ\n' \ '/tdZoiDC0ZNTaNnOSTo765oKKqhHUTQkwkGChrwG3Js5jekV4zpPMLhUafXk6ksd\n' \ '8XLlZdCF3RUnuguXAg2xP/duxMYmTCx3eeGPkXBPQl0pahu8/6OtBoYvBrqNdQcx\n' \ 'jnEtYX9PCqDY3hAXW9GWsxNfu02DKhWigFHFNRUQtMI++438+QIfzXPslE2bTQIt\n' \ '0OXUlwECgYEAxTKUZ7lwIBb5XKPJq53RQmX66M3ArxI1RzFSKm1+/CmxvYiN0c+5\n' \ '2Aq62WEIauX6hoZ7yQb4zhdeNRzinLR7rsmBvIcP12FidXG37q9v3Vu70KmHniJE\n' \ 'TPbt5lHQ0bNACFxkar4Ab/JZN4CkMRgJdlcZ5boYNmcGOYCvw9izuM8CgYEA0iQ1\n' \ 'khIFZ6fCiXwVRGvEHmqSnkBmBHz8MY8fczv2Z4Gzfq3Tlh9VxpigK2F2pFt7keWc\n' \ '53HerYFHFpf5otDhEyRwA1LyIcwbj5HopumxsB2WG+/M2as45lLfWa6KO73OtPpU\n' \ 'wGZYW+i/otdk9eFphceYtw19mxI+3lYoeI8EjYECgYBxOtTKJkmCs45lqkp/d3QT\n' \ '2zjSempcXGkpQuG6KPtUUaCUgxdj1RISQj792OCbeQh8PDZRvOYaeIKInthkQKIQ\n' \ 'P/Z1yVvIQUvmwfBqZmQmR6k1bFLJ80UiqFr7+BiegH2RD3Q9cnIP1aly3DPrWLD+\n' \ 'OY9OQKfsfQWu+PxzyTeRMwKBgD8Zjlh5PtQ8RKcB8mTkMzSq7bHFRpzsZtH+1wPE\n' \ 'Kp40DRDp41H9wMTsiZPdJUH/EmDh4LaCs8nHuu/m3JfuPtd/pn7pBjntzwzSVFji\n' \ 'bW+jwrJK1Gk8B87pbZXBWlLMEOi5Dn/je37Fqd2c7f0DHauFHq9AxsmsteIPXwGs\n' \ 'eEKBAoGBAIzJX/5yFp3ObkPracIfOJ/U/HF1UdP6Y8qmOJBZOg5s9Y+JAdY76raK\n' \ '0SbZPsOpuFUdTiRkSI3w/p1IuM5dPxgCGH9MHqjqogU5QwXr3vLF+a/PFhINkn1x\n' \ 'lozRZjDcF1y6xHfExotPC973UZnKEviq9/FqOsovZpvSQkzAYSZF\n' \ '-----END RSA PRIVATE KEY-----' publicKeyPem = '-----BEGIN PUBLIC KEY-----\n' \ 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAod9iHfIn4ugY/2byFrFj\n' \ 'UprrFLkkH5bCrjiBq2/MdHFg99IQ7li2x2mg5fkBMhU5SJIxlN8kiZMFq7JUXSA9\n' \ '7Yo4puhVubqTSHihIh6Xn2mTjTgszNo9SBbmN3YiyBPTcr0rF4jGWZAduJ8u6i7E\n' \ 'ky2QH+UBKyUNRZrcfoVq+7grHUIA45pE7vAfEEWtgRiw32Nwlx55N3hayHax0y8g\n' \ 'MdKEF/vfYKRLcM7rZgEASMtlCpgyfsyHwFCDzl/BP8AhP9u3dM+SEundeAvF58Ai\n' \ 'Xx1pKvBpxqttDNAsKWCRQ06/WI/W2Rwihl9yCjobqRoFsZ/cTEi6FG9AbDAds5Yj\n' \ 'TwIDAQAB\n' \ '-----END PUBLIC KEY-----' signedDocument = jldDocument.copy() generate_json_signature(signedDocument, privateKeyPem) assert(signedDocument) assert(signedDocument.get('signature')) assert(signedDocument['signature'].get('signatureValue')) assert(signedDocument['signature'].get('type')) assert(len(signedDocument['signature']['signatureValue']) > 50) # print(str(signedDocument['signature'])) assert(signedDocument['signature']['type'] == 'RsaSignature2017') assert(verify_json_signature(signedDocument, publicKeyPem)) # alter the signed document signedDocument['object']['content'] = 'forged content' assert(not verify_json_signature(signedDocument, publicKeyPem)) jldDocument2 = { "@context": "https://www.w3.org/ns/activitystreams", "actor": "https://somesite.net/users/gerbil", "description": "Another json document", "numberField": 13353, "object": { "content": "More content" } } signedDocument2 = jldDocument2.copy() generate_json_signature(signedDocument2, privateKeyPem) assert(signedDocument2) assert(signedDocument2.get('signature')) assert(signedDocument2['signature'].get('signatureValue')) # changed signature on different document if signedDocument['signature']['signatureValue'] == \ signedDocument2['signature']['signatureValue']: print('json signature has not changed for different documents') assert '.' not in str(signedDocument['signature']['signatureValue']) assert len(str(signedDocument['signature']['signatureValue'])) > 340 assert(signedDocument['signature']['signatureValue'] != signedDocument2['signature']['signatureValue']) def _test_site_active(): print('testSiteIsActive') timeout = 10 assert(site_is_active('https://archive.org', timeout)) assert(site_is_active('https://mastodon.social', timeout)) assert(not site_is_active('https://notarealwebsite.a.b.c', timeout)) def _test_strip_html(): print('testRemoveHtml') testStr = 'This string has no html.' assert(remove_html(testStr) == testStr) testStr = 'This string has html.' assert(remove_html(testStr) == 'This string has html.') testStr = '' assert(remove_html(testStr) == 'This string has. Two labels.') testStr = 'This string has.
Two paragraphs.
' assert(remove_html(testStr) == 'This string has.\n\nTwo paragraphs.') testStr = 'This string has.This string contains a url http://somesite.or.other
' assert(remove_html(testStr) == 'This string contains a url http://somesite.or.other') def _test_danger_css(base_dir: str) -> None: print('testDangerousCSS') for subdir, dirs, files in os.walk(base_dir): for f in files: if not f.endswith('.css'): continue assert not dangerous_css(base_dir + '/' + f, False) break def _test_danger_svg(base_dir: str) -> None: print('testDangerousSVG') svgContent = \ ' ' assert not dangerous_svg(svgContent, False) svgContent = \ ' ' assert dangerous_svg(svgContent, False) assert not scan_themes_for_scripts(base_dir) def _test_danger_markup(): print('testDangerousMarkup') allow_local_network_access = False content = 'This is a valid message
' assert(not dangerous_markup(content, allow_local_network_access)) content = 'This is a valid message without markup' assert(not dangerous_markup(content, allow_local_network_access)) content = 'This is a valid-looking message. But wait... ' + \ '
' assert(dangerous_markup(content, allow_local_network_access)) content = 'This is a valid-looking message. But wait... ' + \ '<script>document.getElementById("concentrated")' + \ '.innerHTML = "evil";</script>
' assert(dangerous_markup(content, allow_local_network_access)) content = 'This html contains more than you expected... ' + \ '
' assert(dangerous_markup(content, allow_local_network_access)) content = 'This is a valid-looking message. But wait... ' + \ '
' assert(dangerous_markup(content, allow_local_network_access)) content = 'This message embeds an evil frame.' + \ '
' assert(dangerous_markup(content, allow_local_network_access)) content = 'This message tries to obfuscate an evil frame.' + \ '< iframe src = "somesite"> iframe >
' assert(dangerous_markup(content, allow_local_network_access)) content = 'This message is not necessarily evil, but annoying.' + \ '
This message contans a ' + \ 'valid link.
' assert(not dangerous_markup(content, allow_local_network_access)) content = 'This message contans a ' + \ '' + \ 'valid link having invalid but harmless name.
' assert(not dangerous_markup(content, allow_local_network_access)) content = 'This message which ' + \ 'tries to access the local network
' assert(dangerous_markup(content, allow_local_network_access)) content = 'This message which ' + \ 'tries to access the local network
' assert(dangerous_markup(content, allow_local_network_access)) content = '127.0.0.1 This message which does not access ' + \ 'the local network
' assert(not dangerous_markup(content, allow_local_network_access)) def _run_html_replace_quote_marks(): print('html_replace_quote_marks') testStr = 'The "cat" "sat" on the mat' result = html_replace_quote_marks(testStr) assert result == 'The “cat” “sat” on the mat' testStr = 'The cat sat on the mat' result = html_replace_quote_marks(testStr) assert result == 'The cat sat on the mat' testStr = '"hello"' result = html_replace_quote_marks(testStr) assert result == '“hello”' testStr = '"hello" "test" html' result = html_replace_quote_marks(testStr) assert result == '“hello” “test” html' def _test_json_post_allows_comment(): print('testJsonPostAllowsComments') post_json_object = { "id": "123" } assert json_post_allows_comments(post_json_object) post_json_object = { "id": "123", "commentsEnabled": False } assert not json_post_allows_comments(post_json_object) post_json_object = { "id": "123", "rejectReplies": False } assert json_post_allows_comments(post_json_object) post_json_object = { "id": "123", "rejectReplies": True } assert not json_post_allows_comments(post_json_object) post_json_object = { "id": "123", "commentsEnabled": True } assert json_post_allows_comments(post_json_object) post_json_object = { "id": "123", "object": { "commentsEnabled": True } } assert json_post_allows_comments(post_json_object) post_json_object = { "id": "123", "object": { "commentsEnabled": False } } assert not json_post_allows_comments(post_json_object) def _test_remove_id_ending(): print('testRemoveIdEnding') testStr = 'https://activitypub.somedomain.net' resultStr = remove_id_ending(testStr) assert resultStr == 'https://activitypub.somedomain.net' testStr = \ 'https://activitypub.somedomain.net/users/foo/' + \ 'statuses/34544814814/activity' resultStr = remove_id_ending(testStr) assert resultStr == \ 'https://activitypub.somedomain.net/users/foo/statuses/34544814814' testStr = \ 'https://undo.somedomain.net/users/foo/statuses/34544814814/undo' resultStr = remove_id_ending(testStr) assert resultStr == \ 'https://undo.somedomain.net/users/foo/statuses/34544814814' testStr = \ 'https://event.somedomain.net/users/foo/statuses/34544814814/event' resultStr = remove_id_ending(testStr) assert resultStr == \ 'https://event.somedomain.net/users/foo/statuses/34544814814' def _test_valid_content_warning(): print('testValidContentWarning') resultStr = valid_content_warning('Valid content warning') assert resultStr == 'Valid content warning' resultStr = valid_content_warning('Invalid #content warning') assert resultStr == 'Invalid content warning' resultStr = \ valid_content_warning('Invalid content warning') assert resultStr == 'Invalid content warning' def _test_translations(base_dir: str) -> None: print('testTranslations') languagesStr = get_supported_languages(base_dir) assert languagesStr # load all translations into a dict langDict = {} for lang in languagesStr: langJson = load_json('translations/' + lang + '.json') if not langJson: print('Missing language file ' + 'translations/' + lang + '.json') assert langJson langDict[lang] = langJson # load english translations translationsJson = load_json('translations/en.json') # test each english string exists in the other language files for englishStr, translatedStr in translationsJson.items(): for lang in languagesStr: langJson = langDict[lang] if not langJson.get(englishStr): print(englishStr + ' is missing from ' + lang + '.json') assert langJson.get(englishStr) def _test_constant_time_string(): print('testConstantTimeStringCheck') assert constant_time_string_check('testing', 'testing') assert not constant_time_string_check('testing', '1234') assert not constant_time_string_check('testing', '1234567') itterations = 256 start = time.time() test_str = 'nnjfbefefbsnjsdnvbcueftqfeuqfbqefnjeniwufgy' for timingTest in range(itterations): constant_time_string_check(test_str, test_str) end = time.time() avTime1 = ((end - start) * 1000000 / itterations) # change a single character and observe timing difference start = time.time() for timingTest in range(itterations): constant_time_string_check(test_str, test_str) end = time.time() avTime2 = ((end - start) * 1000000 / itterations) timeDiffMicroseconds = abs(avTime2 - avTime1) # time difference should be less than 10uS assert int(timeDiffMicroseconds) < 10 # change multiple characters and observe timing difference start = time.time() test_str2 = 'ano1befffbsn7sd3vbluef6qseuqfpqeznjgni9bfgi' for timingTest in range(itterations): constant_time_string_check(test_str, test_str2) end = time.time() avTime2 = ((end - start) * 1000000 / itterations) timeDiffMicroseconds = abs(avTime2 - avTime1) # time difference should be less than 10uS assert int(timeDiffMicroseconds) < 10 def _test_replace_email_quote(): print('testReplaceEmailQuote') testStr = 'This content has no quote.
' assert html_replace_email_quote(testStr) == testStr testStr = 'This content has no quote.
' + \ 'With multiple
lines
' assert html_replace_email_quote(testStr) == testStr testStr = '"This is a quoted paragraph."
' assert html_replace_email_quote(testStr) == \ 'This is a quoted paragraph.' testStr = "
" + \
"@nickname " + \
"
> This is a quote
Some other text.
" expectedStr = "" + \
"@nickname " + \
"
This is a quote" + \ "
Some other text.
" resultStr = html_replace_email_quote(testStr) if resultStr != expectedStr: print('Result: ' + str(resultStr)) print('Expect: ' + expectedStr) assert resultStr == expectedStr testStr = "Some text:
> first line->second line
" + \ "Some question?
" expectedStr = "Some text:
first line-
" + \ "second line
Some question?
" resultStr = html_replace_email_quote(testStr) if resultStr != expectedStr: print('Result: ' + str(resultStr)) print('Expect: ' + expectedStr) assert resultStr == expectedStr testStr = "" + \ "@somenick" + \ "
> Text1.
>
" + \
"> Text2
>
> Text3
" + \
">
> Text4
>
> " + \
"Text5
>
> Text6
Text7
" expectedStr = "" + \ "@somenick" + \ "
Text1.
" + \ "Text2
Text3
>
Text4
" + \ "
Text5
Text6
Text7
" resultStr = html_replace_email_quote(testStr) if resultStr != expectedStr: print('Result: ' + str(resultStr)) print('Expect: ' + expectedStr) assert resultStr == expectedStr def _test_strip_html_tag(): print('testRemoveHtmlTag') testStr = "" resultStr = remove_html_tag(testStr, 'width') assert resultStr == "" def _test_hashtag_rules(): print('testHashtagRuleTree') operators = ('not', 'and', 'or', 'xor', 'from', 'contains') url = 'testsite.com' moderated = True conditionsStr = \ 'contains "Cat" or contains "Corvid" or ' + \ 'contains "Dormouse" or contains "Buzzard"' tagsInConditions = [] tree = hashtag_rule_tree(operators, conditionsStr, tagsInConditions, moderated) assert str(tree) == str(['or', ['contains', ['"Cat"']], ['contains', ['"Corvid"']], ['contains', ['"Dormouse"']], ['contains', ['"Buzzard"']]]) content = 'This is a test' moderated = True conditionsStr = '#foo or #bar' tagsInConditions = [] tree = hashtag_rule_tree(operators, conditionsStr, tagsInConditions, moderated) assert str(tree) == str(['or', ['#foo'], ['#bar']]) assert str(tagsInConditions) == str(['#foo', '#bar']) hashtags = ['#foo'] assert hashtag_rule_resolve(tree, hashtags, moderated, content, url) hashtags = ['#carrot', '#stick'] assert not hashtag_rule_resolve(tree, hashtags, moderated, content, url) content = 'This is a test' url = 'https://testsite.com/something' moderated = True conditionsStr = '#foo and from "testsite.com"' tagsInConditions = [] tree = hashtag_rule_tree(operators, conditionsStr, tagsInConditions, moderated) assert str(tree) == str(['and', ['#foo'], ['from', ['"testsite.com"']]]) assert str(tagsInConditions) == str(['#foo']) hashtags = ['#foo'] assert hashtag_rule_resolve(tree, hashtags, moderated, content, url) assert not hashtag_rule_resolve(tree, hashtags, moderated, content, 'othersite.net') content = 'This is a test' moderated = True conditionsStr = 'contains "is a" and #foo or #bar' tagsInConditions = [] tree = hashtag_rule_tree(operators, conditionsStr, tagsInConditions, moderated) assert str(tree) == \ str(['and', ['contains', ['"is a"']], ['or', ['#foo'], ['#bar']]]) assert str(tagsInConditions) == str(['#foo', '#bar']) hashtags = ['#foo'] assert hashtag_rule_resolve(tree, hashtags, moderated, content, url) hashtags = ['#carrot', '#stick'] assert not hashtag_rule_resolve(tree, hashtags, moderated, content, url) moderated = False conditionsStr = 'not moderated and #foo or #bar' tagsInConditions = [] tree = hashtag_rule_tree(operators, conditionsStr, tagsInConditions, moderated) assert str(tree) == \ str(['not', ['and', ['moderated'], ['or', ['#foo'], ['#bar']]]]) assert str(tagsInConditions) == str(['#foo', '#bar']) hashtags = ['#foo'] assert hashtag_rule_resolve(tree, hashtags, moderated, content, url) hashtags = ['#carrot', '#stick'] assert hashtag_rule_resolve(tree, hashtags, moderated, content, url) moderated = True conditionsStr = 'moderated and #foo or #bar' tagsInConditions = [] tree = hashtag_rule_tree(operators, conditionsStr, tagsInConditions, moderated) assert str(tree) == \ str(['and', ['moderated'], ['or', ['#foo'], ['#bar']]]) assert str(tagsInConditions) == str(['#foo', '#bar']) hashtags = ['#foo'] assert hashtag_rule_resolve(tree, hashtags, moderated, content, url) hashtags = ['#carrot', '#stick'] assert not hashtag_rule_resolve(tree, hashtags, moderated, content, url) conditionsStr = 'x' tagsInConditions = [] tree = hashtag_rule_tree(operators, conditionsStr, tagsInConditions, moderated) assert tree is None assert tagsInConditions == [] hashtags = ['#foo'] assert not hashtag_rule_resolve(tree, hashtags, moderated, content, url) conditionsStr = '#x' tagsInConditions = [] tree = hashtag_rule_tree(operators, conditionsStr, tagsInConditions, moderated) assert str(tree) == str(['#x']) assert str(tagsInConditions) == str(['#x']) hashtags = ['#x'] assert hashtag_rule_resolve(tree, hashtags, moderated, content, url) hashtags = ['#y', '#z'] assert not hashtag_rule_resolve(tree, hashtags, moderated, content, url) conditionsStr = 'not #b' tagsInConditions = [] tree = hashtag_rule_tree(operators, conditionsStr, tagsInConditions, moderated) assert str(tree) == str(['not', ['#b']]) assert str(tagsInConditions) == str(['#b']) hashtags = ['#y', '#z'] assert hashtag_rule_resolve(tree, hashtags, moderated, content, url) hashtags = ['#a', '#b', '#c'] assert not hashtag_rule_resolve(tree, hashtags, moderated, content, url) conditionsStr = '#foo or #bar and #a' tagsInConditions = [] tree = hashtag_rule_tree(operators, conditionsStr, tagsInConditions, moderated) assert str(tree) == str(['and', ['or', ['#foo'], ['#bar']], ['#a']]) assert str(tagsInConditions) == str(['#foo', '#bar', '#a']) hashtags = ['#foo', '#bar', '#a'] assert hashtag_rule_resolve(tree, hashtags, moderated, content, url) hashtags = ['#bar', '#a'] assert hashtag_rule_resolve(tree, hashtags, moderated, content, url) hashtags = ['#foo', '#a'] assert hashtag_rule_resolve(tree, hashtags, moderated, content, url) hashtags = ['#x', '#a'] assert not hashtag_rule_resolve(tree, hashtags, moderated, content, url) def _test_newswire_tags(): print('testNewswireTags') rssDescription = '' + \ 'Compelling description with #ExcitingHashtag, which is ' + \ 'being posted in #BoringForum' tags = get_newswire_tags(rssDescription, 10) assert len(tags) == 2 assert '#BoringForum' in tags assert '#ExcitingHashtag' in tags def _test_first_paragraph_from_string(): print('testFirstParagraphFromString') testStr = \ '' + \ 'This is another paragraph
' resultStr = first_paragraph_from_string(testStr) if resultStr != 'This is a test': print(resultStr) assert resultStr == 'This is a test' testStr = 'Testing without html' resultStr = first_paragraph_from_string(testStr) assert resultStr == testStr def _test_parse_feed_date(): print('testParseFeedDate') pubDate = "2020-12-14T00:08:06+00:00" publishedDate = parse_feed_date(pubDate) assert publishedDate == "2020-12-14 00:08:06+00:00" pubDate = "Tue, 08 Dec 2020 06:24:38 -0600" publishedDate = parse_feed_date(pubDate) assert publishedDate == "2020-12-08 12:24:38+00:00" pubDate = "2020-08-27T16:12:34+00:00" publishedDate = parse_feed_date(pubDate) assert publishedDate == "2020-08-27 16:12:34+00:00" pubDate = "Sun, 22 Nov 2020 19:51:33 +0100" publishedDate = parse_feed_date(pubDate) assert publishedDate == "2020-11-22 18:51:33+00:00" def _test_valid_nick(): print('testValidNickname') domain = 'somedomain.net' nickname = 'myvalidnick' assert valid_nickname(domain, nickname) nickname = 'my.invalid.nick' assert not valid_nickname(domain, nickname) nickname = 'myinvalidnick?' assert not valid_nickname(domain, nickname) nickname = 'my invalid nick?' assert not valid_nickname(domain, nickname) def _test_guess_tag_category() -> None: print('testGuessHashtagCategory') hashtagCategories = { "foo": ["swan", "goose"], "bar": ["cats", "mouse"] } guess = guess_hashtag_category("unspecifiedgoose", hashtagCategories) assert guess == "foo" guess = guess_hashtag_category("mastocats", hashtagCategories) assert guess == "bar" def _test_mentioned_people(base_dir: str) -> None: print('testGetMentionedPeople') content = "@dragon@cave.site @bat@cave.site This is a test." actors = get_mentioned_people(base_dir, 'https', content, 'mydomain', False) assert actors assert len(actors) == 2 assert actors[0] == "https://cave.site/users/dragon" assert actors[1] == "https://cave.site/users/bat" def _test_reply_to_public_post(base_dir: str) -> None: system_language = 'en' nickname = 'test7492362' domain = 'other.site' port = 443 http_prefix = 'https' post_id = \ http_prefix + '://rat.site/users/ninjarodent/statuses/63746173435' content = "@ninjarodent@rat.site This is a test." followersOnly = False saveToFile = False client_to_server = False commentsEnabled = True attachImageFilename = None mediaType = None imageDescription = 'Some description' city = 'London, England' testInReplyTo = post_id testInReplyToAtomUri = None testSubject = None testSchedulePost = False testEventDate = None testEventTime = None testLocation = None testIsArticle = False conversationId = None low_bandwidth = True content_license_url = 'https://creativecommons.org/licenses/by/4.0' reply = \ create_public_post(base_dir, nickname, domain, port, http_prefix, content, followersOnly, saveToFile, client_to_server, commentsEnabled, attachImageFilename, mediaType, imageDescription, city, testInReplyTo, testInReplyToAtomUri, testSubject, testSchedulePost, testEventDate, testEventTime, testLocation, testIsArticle, system_language, conversationId, low_bandwidth, content_license_url) # print(str(reply)) assert reply['object']['content'] == \ '' + \ '@ninjarodent' + \ ' This is a test.
' reply['object']['contentMap'][system_language] = reply['object']['content'] assert reply['object']['tag'][0]['type'] == 'Mention' assert reply['object']['tag'][0]['name'] == '@ninjarodent@rat.site' assert reply['object']['tag'][0]['href'] == 'https://rat.site/@ninjarodent' assert len(reply['object']['to']) == 1 assert reply['object']['to'][0].endswith('#Public') assert len(reply['object']['cc']) >= 1 assert reply['object']['cc'][0].endswith(nickname + '/followers') assert len(reply['object']['tag']) == 1 if len(reply['object']['cc']) != 2: print('reply["object"]["cc"]: ' + str(reply['object']['cc'])) assert len(reply['object']['cc']) == 2 assert reply['object']['cc'][1] == \ http_prefix + '://rat.site/users/ninjarodent' assert len(reply['to']) == 1 assert reply['to'][0].endswith('#Public') assert len(reply['cc']) >= 1 assert reply['cc'][0].endswith(nickname + '/followers') if len(reply['cc']) != 2: print('reply["cc"]: ' + str(reply['cc'])) assert len(reply['cc']) == 2 assert reply['cc'][1] == http_prefix + '://rat.site/users/ninjarodent' def _get_function_call_args(name: str, lines: [], startLineCtr: int) -> []: """Returns the arguments of a function call given lines of source code and a starting line number """ argsStr = lines[startLineCtr].split(name + '(')[1] if ')' in argsStr: argsStr = argsStr.split(')')[0].replace(' ', '').split(',') return argsStr for lineCtr in range(startLineCtr + 1, len(lines)): if ')' not in lines[lineCtr]: argsStr += lines[lineCtr] continue else: argsStr += lines[lineCtr].split(')')[0] break return argsStr.replace('\n', '').replace(' ', '').split(',') def get_function_calls(name: str, lines: [], startLineCtr: int, functionProperties: {}) -> []: """Returns the functions called by the given one, Starting with the given source code at the given line """ callsFunctions = [] functionContentStr = '' for lineCtr in range(startLineCtr + 1, len(lines)): lineStr = lines[lineCtr].strip() if lineStr.startswith('def '): break if lineStr.startswith('class '): break functionContentStr += lines[lineCtr] for funcName, properties in functionProperties.items(): if funcName + '(' in functionContentStr: callsFunctions.append(funcName) return callsFunctions def _function_args_match(callArgs: [], funcArgs: []): """Do the function artuments match the function call arguments """ if len(callArgs) == len(funcArgs): return True # count non-optional arguments callArgsCtr = 0 for a in callArgs: if a == 'self': continue if '=' not in a or a.startswith("'"): callArgsCtr += 1 funcArgsCtr = 0 for a in funcArgs: if a == 'self': continue if '=' not in a or a.startswith("'"): funcArgsCtr += 1 return callArgsCtr >= funcArgsCtr def _module_in_groups(modName: str, includeGroups: [], modGroups: {}) -> bool: """Is the given module within the included groups list? """ for groupName in includeGroups: if modName in modGroups[groupName]: return True return False def _diagram_groups(includeGroups: [], excludeExtraModules: [], modules: {}, modGroups: {}, maxModuleCalls: int) -> None: """Draws a dot diagram containing only the given module groups """ callGraphStr = 'digraph EpicyonGroups {\n\n' callGraphStr += ' graph [fontsize=10 fontname="Verdana" compound=true];\n' callGraphStr += ' node [fontsize=10 fontname="Verdana"];\n\n' excludeModulesFromDiagram = [ 'setup', 'tests', '__init__', 'pyjsonld' ] excludeModulesFromDiagram += excludeExtraModules # colors of modules nodes for modName, modProperties in modules.items(): if modName in excludeModulesFromDiagram: continue if not _module_in_groups(modName, includeGroups, modGroups): continue if not modProperties.get('calls'): callGraphStr += ' "' + modName + \ '" [fillcolor=yellow style=filled];\n' continue if len(modProperties['calls']) <= int(maxModuleCalls / 8): callGraphStr += ' "' + modName + \ '" [fillcolor=green style=filled];\n' elif len(modProperties['calls']) < int(maxModuleCalls / 4): callGraphStr += ' "' + modName + \ '" [fillcolor=orange style=filled];\n' else: callGraphStr += ' "' + modName + \ '" [fillcolor=red style=filled];\n' callGraphStr += '\n' # connections between modules for modName, modProperties in modules.items(): if modName in excludeModulesFromDiagram: continue if not _module_in_groups(modName, includeGroups, modGroups): continue if not modProperties.get('calls'): continue for modCall in modProperties['calls']: if modCall in excludeModulesFromDiagram: continue if not _module_in_groups(modCall, includeGroups, modGroups): continue callGraphStr += ' "' + modName + '" -> "' + modCall + '";\n' # module groups/clusters clusterCtr = 1 for groupName, groupModules in modGroups.items(): if groupName not in includeGroups: continue callGraphStr += '\n' callGraphStr += \ ' subgraph cluster_' + str(clusterCtr) + ' {\n' callGraphStr += ' node [style=filled];\n' for modName in groupModules: if modName not in excludeModulesFromDiagram: callGraphStr += ' ' + modName + ';\n' callGraphStr += ' label = "' + groupName + '";\n' callGraphStr += ' color = blue;\n' callGraphStr += ' }\n' clusterCtr += 1 callGraphStr += '\n}\n' filename = 'epicyon_groups' for groupName in includeGroups: filename += '_' + groupName.replace(' ', '-') filename += '.dot' with open(filename, 'w+') as fp: fp.write(callGraphStr) print('Graph saved to ' + filename) print('Plot using: ' + 'sfdp -x -Goverlap=false -Goverlap_scaling=2 ' + '-Gsep=+100 -Tx11 epicyon_modules.dot') def _test_post_variable_names(): print('testPostVariableNames') for subdir, dirs, files in os.walk('.'): for source_file in files: if not source_file.endswith('.py'): continue if source_file.startswith('.#'): continue if source_file.startswith('flycheck_'): continue source_str = '' with open(source_file, 'r') as file_source: source_str = file_source.read() if not source_str: continue if ' name="' not in source_str: continue names_list = source_str.split(' name="') for index in range(1, len(names_list)): if '"' not in names_list[index]: continue name_var = names_list[index].split('"')[0] if not name_var: continue if ' ' in name_var: continue if '_' in name_var: print(name_var + ' is not camel case POST variable in ' + source_file) assert False break def _test_config_param_names(): print('testConfigParamNames') fnames = ('get_config_param', 'set_config_param') for subdir, dirs, files in os.walk('.'): for source_file in files: if not source_file.endswith('.py'): continue if source_file.startswith('.#'): continue if source_file.startswith('flycheck_'): continue source_str = '' with open(source_file, 'r') as file_source: source_str = file_source.read() if not source_str: continue for fname in fnames: if fname + '(' not in source_str: continue names_list = source_str.split(fname + '(') for index in range(1, len(names_list)): param_var_name = None if '"' in names_list[index]: param_var_name = names_list[index].split('"')[1] elif "'" in names_list[index]: param_var_name = names_list[index].split("'")[1] if not param_var_name: continue if ' ' in param_var_name: continue if '.' in param_var_name: continue if '/' in param_var_name: continue if '__' in param_var_name: continue if 'POST' in param_var_name: continue if param_var_name.isdigit(): continue if '_' in param_var_name: print(fname + ' in ' + source_file + ' should have camel case variable ' + param_var_name) assert False break def _test_source_contains_no_tabs(): print('testSourceContainsNoTabs') for subdir, dirs, files in os.walk('.'): for source_file in files: if not source_file.endswith('.py'): continue if source_file.startswith('.#'): continue if source_file.startswith('flycheck_'): continue source_str = '' with open(source_file, 'r') as file_source: source_str = file_source.read() if not source_str: continue if '\t' in source_str: print(source_file + ' contains tabs') assert False break def _test_checkbox_names(): print('testCheckboxNames') fnames = ['edit_text_field', 'edit_check_box', 'edit_text_area'] for subdir, dirs, files in os.walk('.'): for source_file in files: if not source_file.endswith('.py'): continue if source_file.startswith('.#'): continue if source_file.startswith('flycheck_'): continue source_str = '' with open(source_file, 'r') as file_source: source_str = file_source.read() if not source_str: continue for fname in fnames: if fname + '(' not in source_str: continue names_list = source_str.split(fname + '(') for index in range(1, len(names_list)): if ')' not in names_list[index]: continue allparams = names_list[index].split(')')[0] if ',' not in allparams: continue allparams_list = allparams.split(',') if len(allparams_list) < 2: continue param_var_name = allparams_list[1].strip() param_var_name = param_var_name.replace('"', '') param_var_name = param_var_name.replace("'", '') if ' ' in param_var_name: continue if '/' in param_var_name: continue if '_' in param_var_name: print(fname + ' in ' + source_file + ' should have camel case variable ' + param_var_name) assert False break def _test_post_field_names(): print('testPOSTfieldNames') fnames = ['fields.get'] source_file = 'daemon.py' source_str = '' with open(source_file, 'r') as file_source: source_str = file_source.read() if not source_str: return for fname in fnames: if fname + '(' not in source_str: continue names_list = source_str.split(fname + '(') for index in range(1, len(names_list)): if ')' not in names_list[index]: continue param_var_name = names_list[index].split(')')[0].strip() orig_param_var_name = fname + '(' + param_var_name + ')' param_var_name = param_var_name.replace('"', '') param_var_name = param_var_name.replace("'", '') if ' ' in param_var_name: continue if '/' in param_var_name: continue if '_' in param_var_name: print(orig_param_var_name + ' in ' + source_file + ' should be camel case') assert False if ' fields[' in source_str: names_list = source_str.split(' fields[') for index in range(1, len(names_list)): if ']' not in names_list[index]: continue param_var_name = names_list[index].split(']')[0].strip() orig_param_var_name = 'fields[' + param_var_name + ']' param_var_name = param_var_name.replace('"', '') param_var_name = param_var_name.replace("'", '') if '_' in param_var_name: print(orig_param_var_name + ' in ' + source_file + ' should be camel case') assert False def _test_functions(): print('testFunctions') function = {} functionProperties = {} modules = {} modGroups = {} methodLOC = [] for subdir, dirs, files in os.walk('.'): for sourceFile in files: if not sourceFile.endswith('.py'): continue if sourceFile.startswith('.#'): continue if sourceFile.startswith('flycheck_'): continue modName = sourceFile.replace('.py', '') modules[modName] = { 'functions': [] } sourceStr = '' with open(sourceFile, 'r') as f: sourceStr = f.read() modules[modName]['source'] = sourceStr with open(sourceFile, 'r') as f: lines = f.readlines() modules[modName]['lines'] = lines lineCount = 0 prevLine = 'start' methodName = '' for line in lines: if '__module_group__' in line: if '=' in line: groupName = line.split('=')[1].strip() groupName = groupName.replace('"', '') groupName = groupName.replace("'", '') modules[modName]['group'] = groupName if not modGroups.get(groupName): modGroups[groupName] = [modName] else: if modName not in modGroups[groupName]: modGroups[groupName].append(modName) if not line.strip().startswith('def '): if lineCount > 0: lineCount += 1 # add LOC count for this function if len(prevLine.strip()) == 0 and \ len(line.strip()) == 0 and \ lineCount > 2: lineCount -= 2 if lineCount > 80: locStr = str(lineCount) + ';' + methodName if lineCount < 1000: locStr = '0' + locStr if lineCount < 100: locStr = '0' + locStr if lineCount < 10: locStr = '0' + locStr if locStr not in methodLOC: methodLOC.append(locStr) lineCount = 0 prevLine = line continue prevLine = line lineCount = 1 methodName = line.split('def ', 1)[1].split('(')[0] methodArgs = \ sourceStr.split('def ' + methodName + '(')[1] methodArgs = methodArgs.split(')')[0] methodArgs = methodArgs.replace(' ', '').split(',') if function.get(modName): function[modName].append(methodName) else: function[modName] = [methodName] if methodName not in modules[modName]['functions']: modules[modName]['functions'].append(methodName) functionProperties[methodName] = { "args": methodArgs, "module": modName, "calledInModule": [] } # LOC count for the last function if lineCount > 2: lineCount -= 2 if lineCount > 80: locStr = str(lineCount) + ';' + methodName if lineCount < 1000: locStr = '0' + locStr if lineCount < 100: locStr = '0' + locStr if lineCount < 10: locStr = '0' + locStr if locStr not in methodLOC: methodLOC.append(locStr) break print('LOC counts:') methodLOC.sort() for locStr in methodLOC: print(locStr.split(';')[0] + ' ' + locStr.split(';')[1]) excludeFuncArgs = [ 'pyjsonld' ] excludeFuncs = [ 'link', 'set', 'get' ] bad_function_names = [] for name, properties in functionProperties.items(): if '_' not in name: if name.lower() != name: bad_function_names.append(name) else: if name.startswith('_'): name2 = name[1:] if '_' not in name2: if name2.lower() != name2: bad_function_names.append(name) if bad_function_names: bad_function_names2 = \ sorted(bad_function_names, key=len, reverse=True) function_names_str = '' function_names_sh = '' ctr = 0 for name in bad_function_names2: if ctr > 0: function_names_str += '\n' + name else: function_names_str = name snake_case_name = convert_to_snake_case(name) function_names_sh += \ 'sed -i "s|' + name + '|' + snake_case_name + '|g" *.py\n' ctr += 1 print(function_names_str + '\n') with open('scripts/bad_function_names.sh', 'w+') as fn_file: fn_file.write(function_names_sh) assert False # which modules is each function used within? for modName, modProperties in modules.items(): print('Module: ' + modName + ' ✓') for name, properties in functionProperties.items(): lineCtr = 0 for line in modules[modName]['lines']: lineStr = line.strip() if lineStr.startswith('def '): lineCtr += 1 continue if lineStr.startswith('class '): lineCtr += 1 continue if name + '(' in line: modList = \ functionProperties[name]['calledInModule'] if modName not in modList: modList.append(modName) if modName in excludeFuncArgs: lineCtr += 1 continue if name in excludeFuncs: lineCtr += 1 continue callArgs = \ _get_function_call_args(name, modules[modName]['lines'], lineCtr) funcArgs = functionProperties[name]['args'] if not _function_args_match(callArgs, funcArgs): print('Call to function ' + name + ' does not match its arguments') print('def args: ' + str(len(functionProperties[name]['args'])) + '\n' + str(functionProperties[name]['args'])) print('Call args: ' + str(len(callArgs)) + '\n' + str(callArgs)) print('module ' + modName + ' line ' + str(lineCtr)) assert False lineCtr += 1 # don't check these functions, because they are procedurally called exclusions = [ 'do_GET', 'do_POST', 'do_HEAD', '__run', '_send_to_named_addresses', 'globaltrace', 'localtrace', 'kill', 'clone', 'unregister_rdf_parser', 'set_document_loader', 'has_property', 'has_value', 'add_value', 'get_values', 'remove_property', 'remove_value', 'normalize', 'get_document_loader', 'run_inbox_queue_watchdog', 'run_inbox_queue', 'run_post_schedule', 'run_post_schedule_watchdog', 'str2bool', 'run_newswire_daemon', 'run_newswire_watchdog', 'run_federated_shares_watchdog', 'run_federated_shares_daemon', 'fitness_thread', 'thread_send_post', 'send_to_followers', 'expire_cache', 'get_mutuals_of_person', 'run_posts_queue', 'run_shares_expire', 'run_posts_watchdog', 'run_shares_expire_watchdog', 'get_this_weeks_events', 'get_availability', '_test_threads_function', 'create_server_group', 'create_server_alice', 'create_server_bob', 'create_server_eve', 'e2e_eremove_device', 'setOrganizationScheme', 'fill_headers', '_nothing' ] excludeImports = [ 'link', 'start' ] excludeLocal = [ 'pyjsonld', 'daemon', 'tests' ] excludeMods = [ 'pyjsonld' ] # check that functions are called somewhere for name, properties in functionProperties.items(): if name.startswith('__'): if name.endswith('__'): continue if name in exclusions: continue if properties['module'] in excludeMods: continue isLocalFunction = False if not properties['calledInModule']: print('function ' + name + ' in module ' + properties['module'] + ' is not called anywhere') assert properties['calledInModule'] if len(properties['calledInModule']) == 1: modName = properties['calledInModule'][0] if modName not in excludeLocal and \ modName == properties['module']: isLocalFunction = True if not name.startswith('_'): print('Local function ' + name + ' in ' + modName + '.py does not begin with _') assert False if name not in excludeImports: for modName in properties['calledInModule']: if modName == properties['module']: continue importStr = 'from ' + properties['module'] + ' import ' + name if importStr not in modules[modName]['source']: print(importStr + ' not found in ' + modName + '.py') assert False if not isLocalFunction: if name.startswith('_'): excludePublic = [ 'pyjsonld', 'daemon', 'tests' ] modName = properties['module'] if modName not in excludePublic: print('Public function ' + name + ' in ' + modName + '.py begins with _') assert False print('Function: ' + name + ' ✓') print('Constructing function call graph') moduleColors = ( 'red', 'green', 'yellow', 'orange', 'purple', 'cyan', 'darkgoldenrod3', 'darkolivegreen1', 'darkorange1', 'darkorchid1', 'darkseagreen', 'darkslategray4', 'deeppink1', 'deepskyblue1', 'dimgrey', 'gold1', 'goldenrod', 'burlywood2', 'bisque1', 'brown1', 'chartreuse2', 'cornsilk', 'darksalmon' ) maxModuleCalls = 1 maxFunctionCalls = 1 colorCtr = 0 for modName, modProperties in modules.items(): lineCtr = 0 modules[modName]['color'] = moduleColors[colorCtr] colorCtr += 1 if colorCtr >= len(moduleColors): colorCtr = 0 for line in modules[modName]['lines']: if line.strip().startswith('def '): name = line.split('def ')[1].split('(')[0] callsList = \ get_function_calls(name, modules[modName]['lines'], lineCtr, functionProperties) functionProperties[name]['calls'] = callsList.copy() if len(callsList) > maxFunctionCalls: maxFunctionCalls = len(callsList) # keep track of which module calls which other module for fn in callsList: modCall = functionProperties[fn]['module'] if modCall != modName: if modules[modName].get('calls'): if modCall not in modules[modName]['calls']: modules[modName]['calls'].append(modCall) if len(modules[modName]['calls']) > \ maxModuleCalls: maxModuleCalls = \ len(modules[modName]['calls']) else: modules[modName]['calls'] = [modCall] lineCtr += 1 _diagram_groups(['Commandline Interface', 'ActivityPub'], ['utils'], modules, modGroups, maxModuleCalls) _diagram_groups(['Commandline Interface', 'Core'], ['utils'], modules, modGroups, maxModuleCalls) _diagram_groups(['Timeline', 'Core'], ['utils'], modules, modGroups, maxModuleCalls) _diagram_groups(['Web Interface', 'Core'], ['utils'], modules, modGroups, maxModuleCalls) _diagram_groups(['Web Interface Columns', 'Core'], ['utils'], modules, modGroups, maxModuleCalls) _diagram_groups(['Core'], [], modules, modGroups, maxModuleCalls) _diagram_groups(['ActivityPub'], [], modules, modGroups, maxModuleCalls) _diagram_groups(['ActivityPub', 'Core'], ['utils'], modules, modGroups, maxModuleCalls) _diagram_groups(['ActivityPub', 'Security'], ['utils'], modules, modGroups, maxModuleCalls) _diagram_groups(['Core', 'Security'], ['utils'], modules, modGroups, maxModuleCalls) _diagram_groups(['Timeline', 'Security'], ['utils'], modules, modGroups, maxModuleCalls) _diagram_groups(['Web Interface', 'Accessibility'], ['utils', 'webapp_utils'], modules, modGroups, maxModuleCalls) _diagram_groups(['Core', 'Accessibility'], ['utils'], modules, modGroups, maxModuleCalls) def _test_links_within_post(base_dir: str) -> None: system_language = 'en' nickname = 'test27636' domain = 'rando.site' port = 443 http_prefix = 'https' content = 'This is a test post with links.\n\n' + \ 'ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/v4/\n\nhttps://libreserver.org' followersOnly = False saveToFile = False client_to_server = False commentsEnabled = True attachImageFilename = None mediaType = None imageDescription = None city = 'London, England' testInReplyTo = None testInReplyToAtomUri = None testSubject = None testSchedulePost = False testEventDate = None testEventTime = None testLocation = None testIsArticle = False conversationId = None low_bandwidth = True content_license_url = 'https://creativecommons.org/licenses/by/4.0' post_json_object = \ create_public_post(base_dir, nickname, domain, port, http_prefix, content, followersOnly, saveToFile, client_to_server, commentsEnabled, attachImageFilename, mediaType, imageDescription, city, testInReplyTo, testInReplyToAtomUri, testSubject, testSchedulePost, testEventDate, testEventTime, testLocation, testIsArticle, system_language, conversationId, low_bandwidth, content_license_url) assert post_json_object['object']['content'] == \ 'This is a test post with links.
' + \
'' + \
'ftp://' + \
'' + \
'ftp.ncdc.noaa.gov/pub/data/ghcn/v4/' + \
'
' + \
'https://' + \
'libreserver.org
Some text
Other text
More text
" + \ "Errno::EOHNOES (No such file or rodent @ " + \
"ik_right - /tmp/blah.png)
" + \ "(" + \ "wuh)
Oh yeah like for sure
" + \ "Ground sloth tin opener
" + \ "" post_json_object = \ create_public_post(base_dir, nickname, domain, port, http_prefix, content, False, False, False, True, None, None, False, None, testInReplyTo, testInReplyToAtomUri, testSubject, testSchedulePost, testEventDate, testEventTime, testLocation, testIsArticle, system_language, conversationId, low_bandwidth, content_license_url) assert post_json_object['object']['content'] == content assert post_json_object['object']['contentMap'][system_language] == content def _test_mastoapi(): print('testMastoApi') nickname = 'ThisIsATestNickname' mastoId = get_masto_api_v1id_from_nickname(nickname) assert(mastoId) nickname2 = get_nickname_from_masto_api_v1id(mastoId) if nickname2 != nickname: print(nickname + ' != ' + nickname2) assert nickname2 == nickname def _test_domain_handling(): print('testDomainHandling') testDomain = 'localhost' assert decoded_host(testDomain) == testDomain testDomain = '127.0.0.1:60' assert decoded_host(testDomain) == testDomain testDomain = '192.168.5.153' assert decoded_host(testDomain) == testDomain testDomain = 'xn--espaa-rta.icom.museum' assert decoded_host(testDomain) == "españa.icom.museum" def _test_prepare_html_post_nick(): print('testPrepareHtmlPostNickname') postHtml = ' Some quote or other' assert markdown_to_html(markdown) == 'This is a quotation:Some quote or other' markdown = 'This is a multi-line quotation:\n' + \ '> The first line\n' + \ '> The second line' assert markdown_to_html(markdown) == \ 'This is a multi-line quotation:
The first line The second line' markdown = 'This is **bold**' assert markdown_to_html(markdown) == 'This is bold' markdown = 'This is *italic*' assert markdown_to_html(markdown) == 'This is italic' markdown = 'This is _underlined_' assert markdown_to_html(markdown) == 'This is
' + remove_html(content) + '
' assert '>@linked' not in contentPlain content = add_links_to_content(contentPlain, links) assert '>@linked' in content def _test_authorized_shared_items(): print('testAuthorizeSharedItems') shared_items_fed_domains = \ ['dog.domain', 'cat.domain', 'birb.domain'] tokensJson = \ generate_shared_item_federation_tokens(shared_items_fed_domains, None) tokensJson = \ create_shared_item_federation_token(None, 'cat.domain', False, tokensJson) assert tokensJson assert not tokensJson.get('dog.domain') assert tokensJson.get('cat.domain') assert not tokensJson.get('birb.domain') assert len(tokensJson['dog.domain']) == 0 assert len(tokensJson['cat.domain']) >= 64 assert len(tokensJson['birb.domain']) == 0 assert not authorize_shared_items(shared_items_fed_domains, None, 'birb.domain', 'cat.domain', 'M' * 86, False, tokensJson) assert authorize_shared_items(shared_items_fed_domains, None, 'birb.domain', 'cat.domain', tokensJson['cat.domain'], False, tokensJson) tokensJson = \ update_shared_item_federation_token(None, 'dog.domain', 'testToken', True, tokensJson) assert tokensJson['dog.domain'] == 'testToken' # the shared item federation list changes shared_items_federated_domains = \ ['possum.domain', 'cat.domain', 'birb.domain'] tokensJson = merge_shared_item_tokens(None, '', shared_items_federated_domains, tokensJson) assert 'dog.domain' not in tokensJson assert 'cat.domain' in tokensJson assert len(tokensJson['cat.domain']) >= 64 assert 'birb.domain' in tokensJson assert 'possum.domain' in tokensJson assert len(tokensJson['birb.domain']) == 0 assert len(tokensJson['possum.domain']) == 0 def _test_date_conversions() -> None: print('testDateConversions') dateStr = "2021-05-16T14:37:41Z" dateSec = date_string_to_seconds(dateStr) dateStr2 = date_seconds_to_string(dateSec) assert dateStr == dateStr2 def _test_valid_password(): print('testValidPassword') assert not valid_password('123') assert not valid_password('') assert valid_password('パスワード12345') assert valid_password('测试密码12345') assert valid_password('A!bc:defg1/234?56') def _test_get_price_from_string() -> None: print('testGetPriceFromString') price, curr = get_price_from_string("5.23") assert price == "5.23" assert curr == "EUR" price, curr = get_price_from_string("£7.36") assert price == "7.36" assert curr == "GBP" price, curr = get_price_from_string("$10.63") assert price == "10.63" assert curr == "USD" def _translate_ontology(base_dir: str) -> None: return ontologyTypes = get_category_types(base_dir) url = 'https://translate.astian.org' apiKey = None ltLangList = libretranslate_languages(url, apiKey) languagesStr = get_supported_languages(base_dir) assert languagesStr for oType in ontologyTypes: changed = False filename = base_dir + '/ontology/' + oType + 'Types.json' if not os.path.isfile(filename): continue ontologyJson = load_json(filename) if not ontologyJson: continue index = -1 for item in ontologyJson['@graph']: index += 1 if "rdfs:label" not in item: continue englishStr = None languagesFound = [] for label in item["rdfs:label"]: if '@language' not in label: continue languagesFound.append(label['@language']) if '@value' not in label: continue if label['@language'] == 'en': englishStr = label['@value'] if not englishStr: continue for lang in languagesStr: if lang not in languagesFound: translatedStr = None if url and lang in ltLangList: translatedStr = \ libretranslate(url, englishStr, 'en', lang, apiKey) if not translatedStr: translatedStr = englishStr else: translatedStr = translatedStr.replace('', '') translatedStr = translatedStr.replace('
', '') ontologyJson['@graph'][index]["rdfs:label"].append({ "@value": translatedStr, "@language": lang }) changed = True if not changed: continue save_json(ontologyJson, filename + '.new') def _test_can_replyto(base_dir: str) -> None: print('testCanReplyTo') system_language = 'en' nickname = 'test27637' domain = 'rando.site' port = 443 http_prefix = 'https' content = 'This is a test post with links.\n\n' + \ 'ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/v4/\n\nhttps://libreserver.org' followersOnly = False saveToFile = False client_to_server = False commentsEnabled = True attachImageFilename = None mediaType = None imageDescription = None city = 'London, England' testInReplyTo = None testInReplyToAtomUri = None testSubject = None testSchedulePost = False testEventDate = None testEventTime = None testLocation = None testIsArticle = False conversationId = None low_bandwidth = True content_license_url = 'https://creativecommons.org/licenses/by/4.0' post_json_object = \ create_public_post(base_dir, nickname, domain, port, http_prefix, content, followersOnly, saveToFile, client_to_server, commentsEnabled, attachImageFilename, mediaType, imageDescription, city, testInReplyTo, testInReplyToAtomUri, testSubject, testSchedulePost, testEventDate, testEventTime, testLocation, testIsArticle, system_language, conversationId, low_bandwidth, content_license_url) # set the date on the post currDateStr = "2021-09-08T20:45:00Z" post_json_object['published'] = currDateStr post_json_object['object']['published'] = currDateStr # test a post within the reply interval postUrl = post_json_object['object']['id'] replyIntervalHours = 2 currDateStr = "2021-09-08T21:32:10Z" assert can_reply_to(base_dir, nickname, domain, postUrl, replyIntervalHours, currDateStr, post_json_object) # test a post outside of the reply interval currDateStr = "2021-09-09T09:24:47Z" assert not can_reply_to(base_dir, nickname, domain, postUrl, replyIntervalHours, currDateStr, post_json_object) def _test_seconds_between_publish() -> None: print('testSecondsBetweenPublished') published1 = "2021-10-14T09:39:27Z" published2 = "2021-10-14T09:41:28Z" secondsElapsed = seconds_between_published(published1, published2) assert secondsElapsed == 121 # invalid date published2 = "2021-10-14N09:41:28Z" secondsElapsed = seconds_between_published(published1, published2) assert secondsElapsed == -1 def _test_word_similarity() -> None: print('testWordsSimilarity') minWords = 10 content1 = "This is the same" content2 = "This is the same" assert words_similarity(content1, content2, minWords) == 100 content1 = "This is our world now... " + \ "the world of the electron and the switch, the beauty of the baud" content2 = "This is our world now. " + \ "The world of the electron and the webkit, the beauty of the baud" similarity = words_similarity(content1, content2, minWords) assert similarity > 70 content1 = "We're growing!
A new denizen " + \ "is frequenting HackBucket. You probably know him already " + \ "from her epic typos - but let's not spoil too much " + \ "\ud83d\udd2e
" content2 = "We're growing!
A new denizen " + \ "is frequenting HackBucket. You probably know them already " + \ "from their epic typos - but let's not spoil too much " + \ "\ud83d\udd2e
" similarity = words_similarity(content1, content2, minWords) assert similarity > 80 def _test_add_cw_lists(base_dir: str) -> None: print('testAddCWfromLists') translate = {} cw_lists = load_cw_lists(base_dir, True) assert cw_lists post_json_object = { "object": { "sensitive": False, "summary": None, "content": "" } } add_cw_from_lists(post_json_object, cw_lists, translate, 'Murdoch press') assert post_json_object['object']['sensitive'] is False assert post_json_object['object']['summary'] is None post_json_object = { "object": { "sensitive": False, "summary": None, "content": "Blah blah news.co.uk blah blah" } } add_cw_from_lists(post_json_object, cw_lists, translate, 'Murdoch press') assert post_json_object['object']['sensitive'] is True assert post_json_object['object']['summary'] == "Murdoch Press" post_json_object = { "object": { "sensitive": True, "summary": "Existing CW", "content": "Blah blah news.co.uk blah blah" } } add_cw_from_lists(post_json_object, cw_lists, translate, 'Murdoch press') assert post_json_object['object']['sensitive'] is True assert post_json_object['object']['summary'] == \ "Murdoch Press / Existing CW" def _test_valid_emoji_content() -> None: print('testValidEmojiContent') assert not valid_emoji_content(None) assert not valid_emoji_content(' ') assert not valid_emoji_content('j') assert not valid_emoji_content('😀😀😀') assert valid_emoji_content('😀') assert valid_emoji_content('😄') def _test_httpsig_base_new(withDigest: bool, base_dir: str, algorithm: str, digestAlgorithm: str) -> None: print('testHttpsigNew(' + str(withDigest) + ')') debug = True path = base_dir + '/.testHttpsigBaseNew' if os.path.isdir(path): shutil.rmtree(path, ignore_errors=False, onerror=None) os.mkdir(path) os.chdir(path) content_type = 'application/activity+json' nickname = 'socrates' hostDomain = 'someother.instance' domain = 'argumentative.social' http_prefix = 'https' port = 5576 password = 'SuperSecretPassword' privateKeyPem, publicKeyPem, person, wfEndpoint = \ create_person(path, nickname, domain, port, http_prefix, False, False, password) assert privateKeyPem if withDigest: messageBodyJson = { "a key": "a value", "another key": "A string", "yet another key": "Another string" } messageBodyJsonStr = json.dumps(messageBodyJson) else: messageBodyJsonStr = '' headersDomain = get_full_domain(hostDomain, port) dateStr = strftime("%a, %d %b %Y %H:%M:%S %Z", gmtime()) boxpath = '/inbox' if not withDigest: headers = { 'host': headersDomain, 'date': dateStr, 'accept': content_type } signatureIndexHeader, signatureHeader = \ sign_post_headers_new(dateStr, privateKeyPem, nickname, domain, port, hostDomain, port, boxpath, http_prefix, messageBodyJsonStr, algorithm, digestAlgorithm, debug) else: digestPrefix = get_digest_prefix(digestAlgorithm) bodyDigest = \ message_content_digest(messageBodyJsonStr, digestAlgorithm) contentLength = len(messageBodyJsonStr) headers = { 'host': headersDomain, 'date': dateStr, 'digest': f'{digestPrefix}={bodyDigest}', 'content-type': content_type, 'content-length': str(contentLength) } assert get_digest_algorithm_from_headers(headers) == digestAlgorithm signatureIndexHeader, signatureHeader = \ sign_post_headers_new(dateStr, privateKeyPem, nickname, domain, port, hostDomain, port, boxpath, http_prefix, messageBodyJsonStr, algorithm, digestAlgorithm, debug) headers['signature'] = signatureHeader headers['signature-input'] = signatureIndexHeader print('headers: ' + str(headers)) GETmethod = not withDigest debug = True assert verify_post_headers(http_prefix, publicKeyPem, headers, boxpath, GETmethod, None, messageBodyJsonStr, debug) debug = False if withDigest: # everything correct except for content-length headers['content-length'] = str(contentLength + 2) assert verify_post_headers(http_prefix, publicKeyPem, headers, boxpath, GETmethod, None, messageBodyJsonStr, debug) is False assert verify_post_headers(http_prefix, publicKeyPem, headers, '/parambulator' + boxpath, GETmethod, None, messageBodyJsonStr, debug) is False assert verify_post_headers(http_prefix, publicKeyPem, headers, boxpath, not GETmethod, None, messageBodyJsonStr, debug) is False if not withDigest: # fake domain headers = { 'host': 'bogon.domain', 'date': dateStr, 'content-type': content_type } else: # correct domain but fake message messageBodyJsonStr = \ '{"a key": "a value", "another key": "Fake GNUs", ' + \ '"yet another key": "More Fake GNUs"}' contentLength = len(messageBodyJsonStr) digestPrefix = get_digest_prefix(digestAlgorithm) bodyDigest = \ message_content_digest(messageBodyJsonStr, digestAlgorithm) headers = { 'host': domain, 'date': dateStr, 'digest': f'{digestPrefix}={bodyDigest}', 'content-type': content_type, 'content-length': str(contentLength) } assert get_digest_algorithm_from_headers(headers) == digestAlgorithm headers['signature'] = signatureHeader headers['signature-input'] = signatureIndexHeader pprint(headers) assert verify_post_headers(http_prefix, publicKeyPem, headers, boxpath, not GETmethod, None, messageBodyJsonStr, False) is False os.chdir(base_dir) shutil.rmtree(path, ignore_errors=False, onerror=None) def _test_get_actor_from_in_reply_to() -> None: print('testGetActorFromInReplyTo') inReplyTo = \ 'https://fosstodon.org/users/bashrc/statuses/107400700612621140' replyActor = get_actor_from_in_reply_to(inReplyTo) assert replyActor == 'https://fosstodon.org/users/bashrc' inReplyTo = 'https://fosstodon.org/activity/107400700612621140' replyActor = get_actor_from_in_reply_to(inReplyTo) assert replyActor is None def run_all_tests(): base_dir = os.getcwd() print('Running tests...') update_default_themes_list(os.getcwd()) _test_source_contains_no_tabs() _translate_ontology(base_dir) _test_get_price_from_string() _test_post_variable_names() _test_config_param_names() _test_post_field_names() _test_checkbox_names() _test_functions() _test_get_actor_from_in_reply_to() _test_valid_emoji_content() _test_add_cw_lists(base_dir) _test_word_similarity() _test_seconds_between_publish() _test_sign_and_verify() _test_danger_svg(base_dir) _test_can_replyto(base_dir) _test_date_conversions() _test_authorized_shared_items() _test_valid_password() _test_get_links_from_content() _test_set_actor_language() _test_limit_repeted_words() _test_word_lengths_limit() _test_switch_word(base_dir) _test_useragent_domain() _test_roles() _test_skills() _test_spoofed_geolocation() _test_remove_interactions() _test_extract_pgp_public_key() _test_emoji_images() _test_camel_case_split() _test_speaker_replace_link() _test_extract_text_fields_from_post() _test_markdown_to_html() _test_valid_hash_tag() _test_prepare_html_post_nick() _test_domain_handling() _test_mastoapi() _test_links_within_post(base_dir) _test_reply_to_public_post(base_dir) _test_mentioned_people(base_dir) _test_guess_tag_category() _test_valid_nick() _test_parse_feed_date() _test_first_paragraph_from_string() _test_newswire_tags() _test_hashtag_rules() _test_strip_html_tag() _test_replace_email_quote() _test_constant_time_string() _test_translations(base_dir) _test_valid_content_warning() _test_remove_id_ending() _test_json_post_allows_comment() _run_html_replace_quote_marks() _test_danger_css(base_dir) _test_danger_markup() _test_strip_html() _test_site_active() _test_jsonld() _test_remove_txt_formatting() _test_web_links() _test_recent_posts_cache() _test_theme() _test_save_load_json() _test_json_string() _test_get_status_number() _test_addemoji(base_dir) _test_actor_parsing() _test_httpsig(base_dir) _test_http_signed_get(base_dir) _test_http_sig_new('rsa-sha256', 'rsa-sha256') _test_httpsig_base_new(True, base_dir, 'rsa-sha256', 'rsa-sha256') _test_httpsig_base_new(False, base_dir, 'rsa-sha256', 'rsa-sha256') _test_cache() _test_threads() _test_create_person_account(base_dir) _test_authentication(base_dir) _test_followers_of_person(base_dir) _test_followers_on_domain(base_dir) _test_follows(base_dir) _test_group_followers(base_dir) print('Tests succeeded\n')