mirror of https://gitlab.com/bashrc2/epicyon
				
				
				
			
		
			
				
	
	
		
			6129 lines
		
	
	
		
			240 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			6129 lines
		
	
	
		
			240 KiB
		
	
	
	
		
			Python
		
	
	
| __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 getDigestAlgorithmFromHeaders
 | ||
| from httpsig import getDigestPrefix
 | ||
| from httpsig import createSignedHeader
 | ||
| from httpsig import signPostHeaders
 | ||
| from httpsig import signPostHeadersNew
 | ||
| from httpsig import verifyPostHeaders
 | ||
| from httpsig import messageContentDigest
 | ||
| from cache import storePersonInCache
 | ||
| from cache import getPersonFromCache
 | ||
| from threads import threadWithTrace
 | ||
| from daemon import runDaemon
 | ||
| from session import createSession
 | ||
| from session import getJson
 | ||
| from posts import getActorFromInReplyTo
 | ||
| from posts import regenerateIndexForBox
 | ||
| from posts import removePostInteractions
 | ||
| from posts import getMentionedPeople
 | ||
| from posts import validContentWarning
 | ||
| from posts import deleteAllPosts
 | ||
| from posts import createPublicPost
 | ||
| from posts import sendPost
 | ||
| from posts import noOfFollowersOnDomain
 | ||
| from posts import groupFollowersByDomain
 | ||
| from posts import archivePostsForPerson
 | ||
| from posts import sendPostViaServer
 | ||
| from posts import secondsBetweenPublished
 | ||
| from follow import clearFollows
 | ||
| from follow import clearFollowers
 | ||
| from follow import sendFollowRequestViaServer
 | ||
| from follow import sendUnfollowRequestViaServer
 | ||
| from siteactive import siteIsActive
 | ||
| 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 extractPGPPublicKey
 | ||
| from pgp import pgpPublicKeyUpload
 | ||
| from utils import contains_pgp_public_key
 | ||
| from follow import followerOfPerson
 | ||
| from follow import unfollowAccount
 | ||
| from follow import unfollowerOfAccount
 | ||
| from follow import sendFollowRequest
 | ||
| from person import createPerson
 | ||
| from person import createGroup
 | ||
| from person import setDisplayNickname
 | ||
| from person import setBio
 | ||
| # from person import generateRSAKey
 | ||
| from skills import setSkillLevel
 | ||
| from skills import actorSkillValue
 | ||
| from skills import setSkillsFromDict
 | ||
| from skills import actorHasSkill
 | ||
| from roles import setRolesFromList
 | ||
| from roles import setRole
 | ||
| from roles import actorHasRole
 | ||
| from auth import constantTimeStringCheck
 | ||
| from auth import createBasicAuthHeader
 | ||
| from auth import authorizeBasic
 | ||
| from auth import storeBasicCredentials
 | ||
| from like import likePost
 | ||
| from like import sendLikeViaServer
 | ||
| from reaction import reactionPost
 | ||
| from reaction import sendReactionViaServer
 | ||
| from reaction import validEmojiContent
 | ||
| from announce import announcePublic
 | ||
| from announce import sendAnnounceViaServer
 | ||
| from city import parseNogoString
 | ||
| from city import spoofGeolocation
 | ||
| from city import pointInNogo
 | ||
| from media import getImageDimensions
 | ||
| from media import getMediaPath
 | ||
| from media import getAttachmentMediaType
 | ||
| from delete import sendDeleteViaServer
 | ||
| from inbox import jsonPostAllowsComments
 | ||
| from inbox import validInbox
 | ||
| from inbox import validInboxFilenames
 | ||
| from categories import guessHashtagCategory
 | ||
| from content import wordsSimilarity
 | ||
| from content import getPriceFromString
 | ||
| from content import limitRepeatedWords
 | ||
| from content import switchWords
 | ||
| from content import extractTextFieldsInPOST
 | ||
| from content import validHashTag
 | ||
| from content import htmlReplaceEmailQuote
 | ||
| from content import htmlReplaceQuoteMarks
 | ||
| from content import dangerousCSS
 | ||
| from content import addWebLinks
 | ||
| from content import replaceEmojiFromTags
 | ||
| from content import addHtmlTags
 | ||
| from content import removeLongWords
 | ||
| from content import replaceContentDuplicates
 | ||
| from content import removeTextFormatting
 | ||
| from content import remove_htmlTag
 | ||
| from theme import updateDefaultThemesList
 | ||
| from theme import setCSSparam
 | ||
| from theme import scanThemesForScripts
 | ||
| from linked_data_sig import generateJsonSignature
 | ||
| from linked_data_sig import verifyJsonSignature
 | ||
| from newsdaemon import hashtagRuleTree
 | ||
| from newsdaemon import hashtagRuleResolve
 | ||
| from newswire import getNewswireTags
 | ||
| from newswire import parseFeedDate
 | ||
| from newswire import limitWordLengths
 | ||
| from mastoapiv1 import getMastoApiV1IdFromNickname
 | ||
| from mastoapiv1 import getNicknameFromMastoApiV1Id
 | ||
| from webapp_post import prepareHtmlPostNickname
 | ||
| from speaker import speakerReplaceLinks
 | ||
| from markdown import markdownToHtml
 | ||
| from languages import setActorLanguages
 | ||
| from languages import getActorLanguages
 | ||
| from languages import getLinksFromContent
 | ||
| from languages import addLinksToContent
 | ||
| from languages import libretranslate
 | ||
| from languages import libretranslateLanguages
 | ||
| from shares import authorizeSharedItems
 | ||
| from shares import generateSharedItemFederationTokens
 | ||
| from shares import createSharedItemFederationToken
 | ||
| from shares import updateSharedItemFederationToken
 | ||
| from shares import mergeSharedItemTokens
 | ||
| from shares import sendShareViaServer
 | ||
| from shares import getSharedItemsCatalogViaServer
 | ||
| from blocking import loadCWLists
 | ||
| from blocking import addCWfromLists
 | ||
| 
 | ||
| testServerGroupRunning = False
 | ||
| testServerAliceRunning = False
 | ||
| testServerBobRunning = False
 | ||
| testServerEveRunning = False
 | ||
| thrGroup = None
 | ||
| thrAlice = None
 | ||
| thrBob = None
 | ||
| thrEve = None
 | ||
| 
 | ||
| 
 | ||
| def _testHttpSignedGET(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 verifyPostHeaders(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 verifyPostHeaders(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 = \
 | ||
|         createPerson(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 = createSignedHeader(dateStr,
 | ||
|                                          privateKeyPem, nickname,
 | ||
|                                          domain, port,
 | ||
|                                          hostDomain, port,
 | ||
|                                          boxpath, http_prefix, False,
 | ||
|                                          None, accept)
 | ||
| 
 | ||
|     headers['signature'] = signatureHeader['signature']
 | ||
|     GETmethod = not withDigest
 | ||
|     assert verifyPostHeaders(http_prefix, publicKeyPem, headers,
 | ||
|                              boxpath, GETmethod, None,
 | ||
|                              messageBodyJsonStr, debug, noRecencyCheck)
 | ||
|     if os.path.isdir(path):
 | ||
|         shutil.rmtree(path, ignore_errors=False, onerror=None)
 | ||
| 
 | ||
| 
 | ||
| def _testSignAndVerify() -> 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 _testHttpSigNew(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 = getDigestPrefix(digestAlgorithm)
 | ||
|     digestStr = digestPrefix + '=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE='
 | ||
|     bodyDigest = messageContentDigest(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 = \
 | ||
|         signPostHeadersNew(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 verifyPostHeaders(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 verifyPostHeaders(http_prefix, publicKeyPem, headers,
 | ||
|                                  pathStr, False, None,
 | ||
|                                  messageBodyJsonStr, debug, True)
 | ||
| 
 | ||
| 
 | ||
| def _testHttpsigBase(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 = \
 | ||
|         createPerson(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 = \
 | ||
|             signPostHeaders(dateStr, privateKeyPem, nickname,
 | ||
|                             domain, port,
 | ||
|                             hostDomain, port,
 | ||
|                             boxpath, http_prefix, None, content_type,
 | ||
|                             algorithm, None)
 | ||
|     else:
 | ||
|         digestPrefix = getDigestPrefix(digestAlgorithm)
 | ||
|         bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm)
 | ||
|         contentLength = len(messageBodyJsonStr)
 | ||
|         headers = {
 | ||
|             'host': headersDomain,
 | ||
|             'date': dateStr,
 | ||
|             'digest': f'{digestPrefix}={bodyDigest}',
 | ||
|             'content-type': content_type,
 | ||
|             'content-length': str(contentLength)
 | ||
|         }
 | ||
|         assert getDigestAlgorithmFromHeaders(headers) == digestAlgorithm
 | ||
|         signatureHeader = \
 | ||
|             signPostHeaders(dateStr, privateKeyPem, nickname,
 | ||
|                             domain, port,
 | ||
|                             hostDomain, port,
 | ||
|                             boxpath, http_prefix, messageBodyJsonStr,
 | ||
|                             content_type, algorithm, digestAlgorithm)
 | ||
| 
 | ||
|     headers['signature'] = signatureHeader
 | ||
|     GETmethod = not withDigest
 | ||
|     debug = True
 | ||
|     assert verifyPostHeaders(http_prefix, publicKeyPem, headers,
 | ||
|                              boxpath, GETmethod, None,
 | ||
|                              messageBodyJsonStr, debug)
 | ||
|     if withDigest:
 | ||
|         # everything correct except for content-length
 | ||
|         headers['content-length'] = str(contentLength + 2)
 | ||
|         assert verifyPostHeaders(http_prefix, publicKeyPem, headers,
 | ||
|                                  boxpath, GETmethod, None,
 | ||
|                                  messageBodyJsonStr, False) is False
 | ||
|     assert verifyPostHeaders(http_prefix, publicKeyPem, headers,
 | ||
|                              '/parambulator' + boxpath, GETmethod, None,
 | ||
|                              messageBodyJsonStr, False) is False
 | ||
|     assert verifyPostHeaders(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 = getDigestPrefix(digestAlgorithm)
 | ||
|         bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm)
 | ||
|         headers = {
 | ||
|             'host': domain,
 | ||
|             'date': dateStr,
 | ||
|             'digest': f'{digestPrefix}={bodyDigest}',
 | ||
|             'content-type': content_type,
 | ||
|             'content-length': str(contentLength)
 | ||
|         }
 | ||
|         assert getDigestAlgorithmFromHeaders(headers) == digestAlgorithm
 | ||
|     headers['signature'] = signatureHeader
 | ||
|     assert verifyPostHeaders(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 _testHttpsig(base_dir: str):
 | ||
|     _testHttpsigBase(True, base_dir)
 | ||
|     _testHttpsigBase(False, base_dir)
 | ||
| 
 | ||
| 
 | ||
| def _testCache():
 | ||
|     print('testCache')
 | ||
|     personUrl = "cat@cardboard.box"
 | ||
|     personJson = {
 | ||
|         "id": 123456,
 | ||
|         "test": "This is a test"
 | ||
|     }
 | ||
|     person_cache = {}
 | ||
|     storePersonInCache(None, personUrl, personJson, person_cache, True)
 | ||
|     result = getPersonFromCache(None, personUrl, person_cache, True)
 | ||
|     assert result['id'] == 123456
 | ||
|     assert result['test'] == 'This is a test'
 | ||
| 
 | ||
| 
 | ||
| def _testThreadsFunction(param: str):
 | ||
|     for i in range(10000):
 | ||
|         time.sleep(2)
 | ||
| 
 | ||
| 
 | ||
| def _testThreads():
 | ||
|     print('testThreads')
 | ||
|     thr = \
 | ||
|         threadWithTrace(target=_testThreadsFunction,
 | ||
|                         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 createServerAlice(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 = \
 | ||
|         createPerson(path, nickname, domain, port, http_prefix, True,
 | ||
|                      False, password)
 | ||
|     deleteAllPosts(path, nickname, domain, 'inbox')
 | ||
|     deleteAllPosts(path, nickname, domain, 'outbox')
 | ||
|     assert setSkillLevel(path, nickname, domain, 'hacking', 90)
 | ||
|     assert setRole(path, nickname, domain, 'guru')
 | ||
|     if hasFollows:
 | ||
|         follow_person(path, nickname, domain, 'bob', bobAddress,
 | ||
|                       federation_list, False, False)
 | ||
|         followerOfPerson(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'
 | ||
|         createPublicPost(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)
 | ||
|         createPublicPost(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)
 | ||
|         createPublicPost(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)
 | ||
|         regenerateIndexForBox(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')
 | ||
|     runDaemon(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 createServerBob(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 = \
 | ||
|         createPerson(path, nickname, domain, port, http_prefix, True,
 | ||
|                      False, password)
 | ||
|     deleteAllPosts(path, nickname, domain, 'inbox')
 | ||
|     deleteAllPosts(path, nickname, domain, 'outbox')
 | ||
|     if hasFollows and aliceAddress:
 | ||
|         follow_person(path, nickname, domain,
 | ||
|                       'alice', aliceAddress, federation_list, False, False)
 | ||
|         followerOfPerson(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'
 | ||
|         createPublicPost(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)
 | ||
|         createPublicPost(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)
 | ||
|         createPublicPost(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)
 | ||
|         regenerateIndexForBox(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')
 | ||
|     runDaemon(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 createServerEve(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 = \
 | ||
|         createPerson(path, nickname, domain, port, http_prefix, True,
 | ||
|                      False, password)
 | ||
|     deleteAllPosts(path, nickname, domain, 'inbox')
 | ||
|     deleteAllPosts(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')
 | ||
|     runDaemon(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 createServerGroup(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 = \
 | ||
|         createGroup(path, nickname, domain, port, http_prefix, True,
 | ||
|                     password)
 | ||
|     deleteAllPosts(path, nickname, domain, 'inbox')
 | ||
|     deleteAllPosts(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')
 | ||
|     runDaemon(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 testPostMessageBetweenServers(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 = \
 | ||
|         threadWithTrace(target=createServerAlice,
 | ||
|                         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 = \
 | ||
|         threadWithTrace(target=createServerBob,
 | ||
|                         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 = createSession(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 = \
 | ||
|         getImageDimensions(attachedImageFilename)
 | ||
|     assert testImageWidth
 | ||
|     assert testImageHeight
 | ||
|     mediaType = getAttachmentMediaType(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 = \
 | ||
|         sendPost(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 = getMediaPath()
 | ||
|     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 validInbox(bobDir, 'bob', bobDomain)
 | ||
|     assert validInboxFilenames(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)
 | ||
|     followerOfPerson(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 = createSession(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 likePost({}, 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 reactionPost({}, 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
 | ||
|     announcePublic(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 testFollowBetweenServers(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 = \
 | ||
|         threadWithTrace(target=createServerAlice,
 | ||
|                         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 = \
 | ||
|         threadWithTrace(target=createServerBob,
 | ||
|                         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 = createSession(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 = \
 | ||
|         sendFollowRequest(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 validInbox(bobDir, 'bob', bobDomain)
 | ||
|     assert validInboxFilenames(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 = \
 | ||
|         sendPost(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 testSharedItemsFederation(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 = \
 | ||
|         threadWithTrace(target=createServerAlice,
 | ||
|                         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 = \
 | ||
|         threadWithTrace(target=createServerBob,
 | ||
|                         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 = createSession(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 = \
 | ||
|         getJson(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, 'shared_items_federated_domains', bobAddress)
 | ||
|     set_config_param(bobDir, 'shared_items_federated_domains', aliceAddress)
 | ||
| 
 | ||
|     print('*********************************************************')
 | ||
|     print('Alice sends a follow request to Bob')
 | ||
|     os.chdir(aliceDir)
 | ||
|     sessionAlice = createSession(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 = \
 | ||
|         sendFollowRequest(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 validInbox(bobDir, 'bob', bobDomain)
 | ||
|     assert validInboxFilenames(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 = createSession(proxy_type)
 | ||
|     shareJson = \
 | ||
|         sendShareViaServer(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 = \
 | ||
|         sendShareViaServer(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 = \
 | ||
|         sendShareViaServer(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 = \
 | ||
|         getSharedItemsCatalogViaServer(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 = \
 | ||
|         sendPost(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 = getJson(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 testGroupFollow(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 = \
 | ||
|         threadWithTrace(target=createServerAlice,
 | ||
|                         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 = \
 | ||
|         threadWithTrace(target=createServerBob,
 | ||
|                         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 = \
 | ||
|         threadWithTrace(target=createServerGroup,
 | ||
|                         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 = createSession(None)
 | ||
|     profileStr = 'https://www.w3.org/ns/activitystreams'
 | ||
|     asHeader = {
 | ||
|         'Accept': 'application/ld+json; profile="' + profileStr + '"'
 | ||
|     }
 | ||
|     signing_priv_key_pem = None
 | ||
|     outboxJson = getJson(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 = getJson(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 = createSession(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 = \
 | ||
|         sendFollowRequest(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 validInbox(testgroupDir, 'testgroup', testgroupDomain)
 | ||
|     assert validInboxFilenames(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 = createSession(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 = \
 | ||
|         sendFollowRequest(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 validInbox(testgroupDir, 'testgroup', testgroupDomain)
 | ||
|     assert validInboxFilenames(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 = \
 | ||
|         sendPost(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 _testFollowersOfPerson(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)
 | ||
|     createPerson(base_dir, nickname, domain, port,
 | ||
|                  http_prefix, True, False, password)
 | ||
|     createPerson(base_dir, 'maxboardroom', domain, port,
 | ||
|                  http_prefix, True, False, password)
 | ||
|     createPerson(base_dir, 'ultrapancake', domain, port,
 | ||
|                  http_prefix, True, False, password)
 | ||
|     createPerson(base_dir, 'drokk', domain, port,
 | ||
|                  http_prefix, True, False, password)
 | ||
|     createPerson(base_dir, 'sausagedog', domain, port,
 | ||
|                  http_prefix, True, False, password)
 | ||
| 
 | ||
|     clearFollows(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 _testNoOfFollowersOnDomain(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)
 | ||
|     createPerson(base_dir, nickname, domain, port, http_prefix, True,
 | ||
|                  False, password)
 | ||
|     createPerson(base_dir, 'maxboardroom', otherdomain, port,
 | ||
|                  http_prefix, True, False, password)
 | ||
|     createPerson(base_dir, 'ultrapancake', otherdomain, port,
 | ||
|                  http_prefix, True, False, password)
 | ||
|     createPerson(base_dir, 'drokk', otherdomain, port,
 | ||
|                  http_prefix, True, False, password)
 | ||
|     createPerson(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)
 | ||
| 
 | ||
|     followerOfPerson(base_dir, nickname, domain,
 | ||
|                      'cucumber', 'sandwiches.party',
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain,
 | ||
|                      'captainsensible', 'damned.zone',
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'pilchard', 'zombies.attack',
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'drokk', otherdomain,
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'sausagedog', otherdomain,
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'maxboardroom', otherdomain,
 | ||
|                      federation_list, False, False)
 | ||
| 
 | ||
|     followersOnOtherDomain = \
 | ||
|         noOfFollowersOnDomain(base_dir, nickname + '@' + domain, otherdomain)
 | ||
|     assert followersOnOtherDomain == 3
 | ||
| 
 | ||
|     unfollowerOfAccount(base_dir, nickname, domain, 'sausagedog', otherdomain,
 | ||
|                         False, False)
 | ||
|     followersOnOtherDomain = \
 | ||
|         noOfFollowersOnDomain(base_dir, nickname + '@' + domain, otherdomain)
 | ||
|     assert followersOnOtherDomain == 2
 | ||
| 
 | ||
|     os.chdir(currDir)
 | ||
|     shutil.rmtree(base_dir, ignore_errors=False, onerror=None)
 | ||
| 
 | ||
| 
 | ||
| def _testGroupFollowers(base_dir: str) -> None:
 | ||
|     print('testGroupFollowers')
 | ||
| 
 | ||
|     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)
 | ||
|     createPerson(base_dir, nickname, domain, port, http_prefix, True,
 | ||
|                  False, password)
 | ||
| 
 | ||
|     clearFollowers(base_dir, nickname, domain)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'badger', 'wild.domain',
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'squirrel', 'wild.domain',
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'rodent', 'wild.domain',
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'utterly', 'clutterly.domain',
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'zonked', 'zzz.domain',
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'nap', 'zzz.domain',
 | ||
|                      federation_list, False, False)
 | ||
| 
 | ||
|     grouped = groupFollowersByDomain(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 _testFollows(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)
 | ||
|     createPerson(base_dir, nickname, domain, port, http_prefix, True,
 | ||
|                  False, password)
 | ||
| 
 | ||
|     clearFollows(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)
 | ||
|     unfollowAccount(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)
 | ||
| 
 | ||
|     clearFollowers(base_dir, nickname, domain)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'badger', 'wild.com',
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'squirrel', 'secret.com',
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'rodent', 'drainpipe.com',
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(base_dir, nickname, domain, 'batman', 'mesh.com',
 | ||
|                      federation_list, False, False)
 | ||
|     followerOfPerson(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 _testCreatePerson(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 = \
 | ||
|         createPerson(base_dir, nickname, domain, port,
 | ||
|                      http_prefix, True, False, password)
 | ||
|     assert os.path.isfile(base_dir + '/accounts/passwords')
 | ||
|     deleteAllPosts(base_dir, nickname, domain, 'inbox')
 | ||
|     deleteAllPosts(base_dir, nickname, domain, 'outbox')
 | ||
|     setDisplayNickname(base_dir, nickname, domain, 'badger')
 | ||
|     setBio(base_dir, nickname, domain, 'Randomly roaming in your backyard')
 | ||
|     archivePostsForPerson(nickname, domain, base_dir, 'inbox', None, {}, 4)
 | ||
|     archivePostsForPerson(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'
 | ||
|     createPublicPost(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 showTestBoxes(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 _testAuthentication(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 storeBasicCredentials(base_dir, 'othernick', 'otherpass')
 | ||
|     assert storeBasicCredentials(base_dir, 'bad:nick', 'otherpass') is False
 | ||
|     assert storeBasicCredentials(base_dir, 'badnick', 'otherpa:ss') is False
 | ||
|     assert storeBasicCredentials(base_dir, nickname, password)
 | ||
| 
 | ||
|     authHeader = createBasicAuthHeader(nickname, password)
 | ||
|     assert authorizeBasic(base_dir, '/users/' + nickname + '/inbox',
 | ||
|                           authHeader, False)
 | ||
|     assert authorizeBasic(base_dir, '/users/' + nickname,
 | ||
|                           authHeader, False) is False
 | ||
|     assert authorizeBasic(base_dir, '/users/othernick/inbox',
 | ||
|                           authHeader, False) is False
 | ||
| 
 | ||
|     authHeader = createBasicAuthHeader(nickname, password + '1')
 | ||
|     assert authorizeBasic(base_dir, '/users/' + nickname + '/inbox',
 | ||
|                           authHeader, False) is False
 | ||
| 
 | ||
|     password = 'someOtherPassword'
 | ||
|     assert storeBasicCredentials(base_dir, nickname, password)
 | ||
| 
 | ||
|     authHeader = createBasicAuthHeader(nickname, password)
 | ||
|     assert authorizeBasic(base_dir, '/users/' + nickname + '/inbox',
 | ||
|                           authHeader, False)
 | ||
| 
 | ||
|     os.chdir(currDir)
 | ||
|     shutil.rmtree(base_dir, ignore_errors=False, onerror=None)
 | ||
| 
 | ||
| 
 | ||
| def testClientToServer(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 = \
 | ||
|         threadWithTrace(target=createServerAlice,
 | ||
|                         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 = \
 | ||
|         threadWithTrace(target=createServerBob,
 | ||
|                         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 = createSession(proxy_type)
 | ||
|     followersOnly = False
 | ||
|     attachedImageFilename = base_dir + '/img/logo.png'
 | ||
|     mediaType = getAttachmentMediaType(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'
 | ||
|     showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
 | ||
|     showTestBoxes('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 = \
 | ||
|         sendPostViaServer(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)
 | ||
| 
 | ||
|     showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
 | ||
|     showTestBoxes('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)
 | ||
| 
 | ||
|     showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
 | ||
|     showTestBoxes('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 validInbox(bobDir, 'bob', bobDomain)
 | ||
|     assert validInboxFilenames(bobDir, 'bob', bobDomain,
 | ||
|                                aliceDomain, alicePort)
 | ||
| 
 | ||
|     print('\n\nAlice follows Bob')
 | ||
|     signing_priv_key_pem = None
 | ||
|     sendFollowRequestViaServer(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 validInbox(bobDir, 'bob', bobDomain)
 | ||
|     assert validInboxFilenames(bobDir, 'bob', bobDomain,
 | ||
|                                aliceDomain, alicePort)
 | ||
| 
 | ||
|     print('\n\nEVENT: Bob follows Alice')
 | ||
|     sendFollowRequestViaServer(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 = createSession(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))])))
 | ||
|     showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
 | ||
|     showTestBoxes('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))])))
 | ||
|     showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
 | ||
|     showTestBoxes('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')
 | ||
|     sendLikeViaServer(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)
 | ||
|     showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
 | ||
|     showTestBoxes('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')
 | ||
|     sendReactionViaServer(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)
 | ||
|     showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
 | ||
|     showTestBoxes('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))])))
 | ||
|     showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
 | ||
|     showTestBoxes('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
 | ||
|     showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
 | ||
|     showTestBoxes('bob', bobInboxPath, bobOutboxPath)
 | ||
|     print('\n\nEVENT: Bob repeats the post')
 | ||
|     signing_priv_key_pem = None
 | ||
|     sendAnnounceViaServer(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)
 | ||
| 
 | ||
|     showTestBoxes('alice', aliceInboxPath, aliceOutboxPath)
 | ||
|     showTestBoxes('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'
 | ||
|     sendDeleteViaServer(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 validInbox(bobDir, 'bob', bobDomain)
 | ||
|     assert validInboxFilenames(bobDir, 'bob', bobDomain,
 | ||
|                                aliceDomain, alicePort)
 | ||
| 
 | ||
|     print('\n\nEVENT: Alice unfollows Bob')
 | ||
|     password = 'alicepass'
 | ||
|     sendUnfollowRequestViaServer(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 validInbox(bobDir, 'bob', bobDomain)
 | ||
|     assert validInboxFilenames(bobDir, 'bob', bobDomain,
 | ||
|                                aliceDomain, alicePort)
 | ||
|     assert validInbox(aliceDir, 'alice', aliceDomain)
 | ||
|     assert validInboxFilenames(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 _testActorParsing():
 | ||
|     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 _testWebLinks():
 | ||
|     print('testWebLinks')
 | ||
| 
 | ||
|     exampleText = \
 | ||
|         "<p>Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + \
 | ||
|         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + \
 | ||
|         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + \
 | ||
|         "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + \
 | ||
|         " <a href=\"https://domain.ugh/tags/turbot\" class=\"mention " + \
 | ||
|         "hashtag\" rel=\"tag\">#<span>turbot</span></a> <a href=\"" + \
 | ||
|         "https://domain.ugh/tags/haddock\" class=\"mention hashtag\"" + \
 | ||
|         " rel=\"tag\">#<span>haddock</span></a></p>"
 | ||
|     resultText = removeLongWords(exampleText, 40, [])
 | ||
|     assert resultText == "<p>Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + \
 | ||
|         " <a href=\"https://domain.ugh/tags/turbot\" class=\"mention " + \
 | ||
|         "hashtag\" rel=\"tag\">#<span>turbot</span></a> " + \
 | ||
|         "<a href=\"https://domain.ugh/tags/haddock\" " + \
 | ||
|         "class=\"mention hashtag\" rel=\"tag\">#<span>haddock</span></a></p>"
 | ||
| 
 | ||
|     exampleText = \
 | ||
|         '<p><span class=\"h-card\"><a href=\"https://something/@orother' + \
 | ||
|         '\" class=\"u-url mention\">@<span>foo</span></a></span> Some ' + \
 | ||
|         'random text.</p><p>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + \
 | ||
|         'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + \
 | ||
|         'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + \
 | ||
|         'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + \
 | ||
|         'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' + \
 | ||
|         'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</p>'
 | ||
|     resultText = removeLongWords(exampleText, 40, [])
 | ||
|     assert resultText == \
 | ||
|         '<p><span class="h-card"><a href="https://something/@orother"' + \
 | ||
|         ' class="u-url mention">@<span>foo</span></a></span> ' + \
 | ||
|         'Some random text.</p>'
 | ||
| 
 | ||
|     exampleText = \
 | ||
|         'This post has a web links https://somesite.net\n\nAnd some other text'
 | ||
|     linkedText = addWebLinks(exampleText)
 | ||
|     assert \
 | ||
|         '<a href="https://somesite.net" rel="nofollow noopener noreferrer"' + \
 | ||
|         ' target="_blank"><span class="invisible">https://' + \
 | ||
|         '</span><span class="ellipsis">somesite.net</span></a' in linkedText
 | ||
| 
 | ||
|     exampleText = \
 | ||
|         'This post has a very long web link\n\nhttp://' + \
 | ||
|         'cbwebewuvfuftdiudbqd33dddbbyuef23fyug3bfhcyu2fct2' + \
 | ||
|         'cuyqbcbucuwvckiwyfgewfvqejbchevbhwevuevwbqebqekve' + \
 | ||
|         'qvuvjfkf.onion\n\nAnd some other text'
 | ||
|     linkedText = addWebLinks(exampleText)
 | ||
|     assert 'ellipsis' in linkedText
 | ||
| 
 | ||
|     exampleText = \
 | ||
|         '<p>1. HAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAH' + \
 | ||
|         'AHAHAHHAHAHAHAHAHAHAHAHAHAHAHAHHAHAHAHAHAHAHAHAH</p>'
 | ||
|     resultText = removeLongWords(exampleText, 40, [])
 | ||
|     assert resultText == '<p>1. HAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHAHA</p>'
 | ||
| 
 | ||
|     exampleText = \
 | ||
|         '<p>Tox address is 88AB9DED6F9FBEF43E105FB72060A2D89F9B93C74' + \
 | ||
|         '4E8C45AB3C5E42C361C837155AFCFD9D448 </p>'
 | ||
|     resultText = removeLongWords(exampleText, 40, [])
 | ||
|     assert resultText == exampleText
 | ||
| 
 | ||
|     exampleText = \
 | ||
|         'some.incredibly.long.and.annoying.word.which.should.be.removed: ' + \
 | ||
|         'The remaining text'
 | ||
|     resultText = removeLongWords(exampleText, 40, [])
 | ||
|     assert resultText == \
 | ||
|         'some.incredibly.long.and.annoying.word.w\n' + \
 | ||
|         'hich.should.be.removed: The remaining text'
 | ||
| 
 | ||
|     exampleText = \
 | ||
|         '<p>Tox address is 88AB9DED6F9FBEF43E105FB72060A2D89F9B93C74' + \
 | ||
|         '4E8C45AB3C5E42C361C837155AFCFD9D448</p>'
 | ||
|     resultText = removeLongWords(exampleText, 40, [])
 | ||
|     assert resultText == \
 | ||
|         '<p>Tox address is 88AB9DED6F9FBEF43E105FB72060A2D89F9B93C7\n' + \
 | ||
|         '44E8C45AB3C5E42C361C837155AFCFD9D448</p>'
 | ||
| 
 | ||
|     exampleText = \
 | ||
|         '<p>ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCA' + \
 | ||
|         'BCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCAB' + \
 | ||
|         'CABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC' + \
 | ||
|         'ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCA' + \
 | ||
|         'BCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCAB' + \
 | ||
|         'CABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABC' + \
 | ||
|         'ABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCA' + \
 | ||
|         'BCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCABCAB' + \
 | ||
|         'CABCABCABCABCABCABCABCABC</p>'
 | ||
|     resultText = removeLongWords(exampleText, 40, [])
 | ||
|     assert resultText == r'<p>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 = addWebLinks(exampleText)
 | ||
|     resultText = removeLongWords(testFnStr, 40, [])
 | ||
|     assert resultText == exampleText
 | ||
|     assert 'ellipsis' not in resultText
 | ||
| 
 | ||
|     exampleText = \
 | ||
|         '<p>filepopout=' + \
 | ||
|         'TemplateAttachmentRichPopout<<\\p>'
 | ||
|     resultText = replaceContentDuplicates(exampleText)
 | ||
|     assert resultText == \
 | ||
|         '<p>filepopout=' + \
 | ||
|         'TemplateAttachmentRichPopout'
 | ||
| 
 | ||
|     exampleText = \
 | ||
|         '<p>Test1 test2 #YetAnotherExcessivelyLongwindedAndBoringHashtag</p>'
 | ||
|     testFnStr = addWebLinks(exampleText)
 | ||
|     resultText = removeLongWords(testFnStr, 40, [])
 | ||
|     assert(resultText ==
 | ||
|            '<p>Test1 test2 '
 | ||
|            '#YetAnotherExcessivelyLongwindedAndBorin\ngHashtag</p>')
 | ||
| 
 | ||
|     exampleText = \
 | ||
|         "<p>Don't remove a p2p link " + \
 | ||
|         "rad:git:hwd1yrerc3mcgn8ga9rho3dqi4w33nep7kxmqezss4topyfgmexihp" + \
 | ||
|         "33xcw</p>"
 | ||
|     testFnStr = addWebLinks(exampleText)
 | ||
|     resultText = removeLongWords(testFnStr, 40, [])
 | ||
|     assert resultText == exampleText
 | ||
| 
 | ||
| 
 | ||
| def _testAddEmoji(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 = \
 | ||
|         createPerson(base_dir, nickname, domain, port,
 | ||
|                      http_prefix, True, False, 'password')
 | ||
|     contentModified = \
 | ||
|         addHtmlTags(base_dir, http_prefix,
 | ||
|                     nickname, domain, content,
 | ||
|                     recipients, hashtags, True)
 | ||
|     assert ':lemon:' in contentModified
 | ||
|     assert contentModified.startswith('<p>')
 | ||
|     assert contentModified.endswith('</p>')
 | ||
|     tags = []
 | ||
|     for tagName, tag in hashtags.items():
 | ||
|         tags.append(tag)
 | ||
|     content = contentModified
 | ||
|     contentModified = \
 | ||
|         replaceEmojiFromTags(None, base_dir, content, tags, 'content', True)
 | ||
|     # print('contentModified: ' + contentModified)
 | ||
|     assert contentModified == '<p>Emoji 🍋 🍓 🍌</p>'
 | ||
| 
 | ||
|     os.chdir(base_dirOriginal)
 | ||
|     shutil.rmtree(base_dirOriginal + '/.tests',
 | ||
|                   ignore_errors=False, onerror=None)
 | ||
| 
 | ||
| 
 | ||
| def _testGetStatusNumber():
 | ||
|     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 _testJsonString() -> None:
 | ||
|     print('testJsonString')
 | ||
|     filename = '.epicyon_tests_testJsonString.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 _testSaveLoadJson():
 | ||
|     print('testSaveLoadJson')
 | ||
|     testJson = {
 | ||
|         "param1": 3,
 | ||
|         "param2": '"Crème brûlée यह एक परीक्षण ह"'
 | ||
|     }
 | ||
|     testFilename = '.epicyon_tests_testSaveLoadJson.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 _testTheme():
 | ||
|     print('testTheme')
 | ||
|     css = 'somestring --background-value: 24px; --foreground-value: 24px;'
 | ||
|     result = setCSSparam(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 = setCSSparam(css, 'background-value', '32px')
 | ||
|     assert result == \
 | ||
|         'somestring --background-value: 32px; --foreground-value: 24px; ' + \
 | ||
|         '--background-value: 32px;'
 | ||
|     css = '--background-value: 24px; --foreground-value: 24px;'
 | ||
|     result = setCSSparam(css, 'background-value', '32px')
 | ||
|     assert result == '--background-value: 32px; --foreground-value: 24px;'
 | ||
| 
 | ||
| 
 | ||
| def _testRecentPostsCache():
 | ||
|     print('testRecentPostsCache')
 | ||
|     recent_posts_cache = {}
 | ||
|     max_recent_posts = 3
 | ||
|     htmlStr = '<html></html>'
 | ||
|     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 _testRemoveTextFormatting():
 | ||
|     print('testRemoveTextFormatting')
 | ||
|     testStr = '<p>Text without formatting</p>'
 | ||
|     resultStr = removeTextFormatting(testStr)
 | ||
|     assert(resultStr == testStr)
 | ||
|     testStr = '<p>Text <i>with</i> <h3>formatting</h3></p>'
 | ||
|     resultStr = removeTextFormatting(testStr)
 | ||
|     assert(resultStr == '<p>Text with formatting</p>')
 | ||
| 
 | ||
| 
 | ||
| def _testJsonld():
 | ||
|     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 = generateRSAKey()
 | ||
|     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()
 | ||
|     generateJsonSignature(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(verifyJsonSignature(signedDocument, publicKeyPem))
 | ||
| 
 | ||
|     # alter the signed document
 | ||
|     signedDocument['object']['content'] = 'forged content'
 | ||
|     assert(not verifyJsonSignature(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()
 | ||
|     generateJsonSignature(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 _testSiteIsActive():
 | ||
|     print('testSiteIsActive')
 | ||
|     timeout = 10
 | ||
|     assert(siteIsActive('https://archive.org', timeout))
 | ||
|     assert(siteIsActive('https://mastodon.social', timeout))
 | ||
|     assert(not siteIsActive('https://notarealwebsite.a.b.c', timeout))
 | ||
| 
 | ||
| 
 | ||
| def _testRemoveHtml():
 | ||
|     print('testRemoveHtml')
 | ||
|     testStr = 'This string has no html.'
 | ||
|     assert(remove_html(testStr) == testStr)
 | ||
|     testStr = 'This string <a href="1234.567">has html</a>.'
 | ||
|     assert(remove_html(testStr) == 'This string has html.')
 | ||
|     testStr = '<label>This string has.</label><label>Two labels.</label>'
 | ||
|     assert(remove_html(testStr) == 'This string has. Two labels.')
 | ||
|     testStr = '<p>This string has.</p><p>Two paragraphs.</p>'
 | ||
|     assert(remove_html(testStr) == 'This string has.\n\nTwo paragraphs.')
 | ||
|     testStr = 'This string has.<br>A new line.'
 | ||
|     assert(remove_html(testStr) == 'This string has.\nA new line.')
 | ||
|     testStr = '<p>This string contains a url http://somesite.or.other</p>'
 | ||
|     assert(remove_html(testStr) ==
 | ||
|            'This string contains a url http://somesite.or.other')
 | ||
| 
 | ||
| 
 | ||
| def _testDangerousCSS(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 dangerousCSS(base_dir + '/' + f, False)
 | ||
|         break
 | ||
| 
 | ||
| 
 | ||
| def _testDangerousSVG(base_dir: str) -> None:
 | ||
|     print('testDangerousSVG')
 | ||
|     svgContent = \
 | ||
|         '  <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg">' + \
 | ||
|         '  <circle cx="5" cy="5" r="4" />' + \
 | ||
|         '</svg>'
 | ||
|     assert not dangerous_svg(svgContent, False)
 | ||
|     svgContent = \
 | ||
|         '  <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg">' + \
 | ||
|         '  <script>' + \
 | ||
|         '  // <![CDATA[' + \
 | ||
|         "  window.addEventListener('DOMContentLoaded', () => {" + \
 | ||
|         '    function attackScript () {' + \
 | ||
|         '      return `#${OWO}`' + \
 | ||
|         '    }' + \
 | ||
|         '' + \
 | ||
|         "    document.querySelector('circle')." + \
 | ||
|         "addEventListener('click', (e) => {" + \
 | ||
|         '      e.target.style.fill = attackScript()' + \
 | ||
|         '    })' + \
 | ||
|         '  })' + \
 | ||
|         '  // ]]>' + \
 | ||
|         '  </script>' + \
 | ||
|         '' + \
 | ||
|         '  <circle cx="5" cy="5" r="4" />' + \
 | ||
|         '</svg>'
 | ||
|     assert dangerous_svg(svgContent, False)
 | ||
| 
 | ||
|     assert not scanThemesForScripts(base_dir)
 | ||
| 
 | ||
| 
 | ||
| def _testDangerousMarkup():
 | ||
|     print('testDangerousMarkup')
 | ||
|     allow_local_network_access = False
 | ||
|     content = '<p>This is a valid message</p>'
 | ||
|     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 = '<p>This is a valid-looking message. But wait... ' + \
 | ||
|         '<script>document.getElementById("concentrated")' + \
 | ||
|         '.innerHTML = "evil";</script></p>'
 | ||
|     assert(dangerous_markup(content, allow_local_network_access))
 | ||
| 
 | ||
|     content = '<p>This is a valid-looking message. But wait... ' + \
 | ||
|         '<script>document.getElementById("concentrated")' + \
 | ||
|         '.innerHTML = "evil";</script></p>'
 | ||
|     assert(dangerous_markup(content, allow_local_network_access))
 | ||
| 
 | ||
|     content = '<p>This html contains more than you expected... ' + \
 | ||
|         '<script language="javascript">document.getElementById("abc")' + \
 | ||
|         '.innerHTML = "def";</script></p>'
 | ||
|     assert(dangerous_markup(content, allow_local_network_access))
 | ||
| 
 | ||
|     content = '<p>This is a valid-looking message. But wait... ' + \
 | ||
|         '<script src="https://evilsite/payload.js" /></p>'
 | ||
|     assert(dangerous_markup(content, allow_local_network_access))
 | ||
| 
 | ||
|     content = '<p>This message embeds an evil frame.' + \
 | ||
|         '<iframe src="somesite"></iframe></p>'
 | ||
|     assert(dangerous_markup(content, allow_local_network_access))
 | ||
| 
 | ||
|     content = '<p>This message tries to obfuscate an evil frame.' + \
 | ||
|         '<  iframe     src = "somesite"></    iframe  ></p>'
 | ||
|     assert(dangerous_markup(content, allow_local_network_access))
 | ||
| 
 | ||
|     content = '<p>This message is not necessarily evil, but annoying.' + \
 | ||
|         '<hr><br><br><br><br><br><br><br><hr><hr></p>'
 | ||
|     assert(dangerous_markup(content, allow_local_network_access))
 | ||
| 
 | ||
|     content = '<p>This message contans a ' + \
 | ||
|         '<a href="https://validsite/index.html">valid link.</a></p>'
 | ||
|     assert(not dangerous_markup(content, allow_local_network_access))
 | ||
| 
 | ||
|     content = '<p>This message contans a ' + \
 | ||
|         '<a href="https://validsite/iframe.html">' + \
 | ||
|         'valid link having invalid but harmless name.</a></p>'
 | ||
|     assert(not dangerous_markup(content, allow_local_network_access))
 | ||
| 
 | ||
|     content = '<p>This message which <a href="127.0.0.1:8736">' + \
 | ||
|         'tries to access the local network</a></p>'
 | ||
|     assert(dangerous_markup(content, allow_local_network_access))
 | ||
| 
 | ||
|     content = '<p>This message which <a href="http://192.168.5.10:7235">' + \
 | ||
|         'tries to access the local network</a></p>'
 | ||
|     assert(dangerous_markup(content, allow_local_network_access))
 | ||
| 
 | ||
|     content = '<p>127.0.0.1 This message which does not access ' + \
 | ||
|         'the local network</a></p>'
 | ||
|     assert(not dangerous_markup(content, allow_local_network_access))
 | ||
| 
 | ||
| 
 | ||
| def _runHtmlReplaceQuoteMarks():
 | ||
|     print('htmlReplaceQuoteMarks')
 | ||
|     testStr = 'The "cat" "sat" on the mat'
 | ||
|     result = htmlReplaceQuoteMarks(testStr)
 | ||
|     assert result == 'The “cat” “sat” on the mat'
 | ||
| 
 | ||
|     testStr = 'The cat sat on the mat'
 | ||
|     result = htmlReplaceQuoteMarks(testStr)
 | ||
|     assert result == 'The cat sat on the mat'
 | ||
| 
 | ||
|     testStr = '"hello"'
 | ||
|     result = htmlReplaceQuoteMarks(testStr)
 | ||
|     assert result == '“hello”'
 | ||
| 
 | ||
|     testStr = '"hello" <a href="somesite.html">"test" html</a>'
 | ||
|     result = htmlReplaceQuoteMarks(testStr)
 | ||
|     assert result == '“hello” <a href="somesite.html">“test” html</a>'
 | ||
| 
 | ||
| 
 | ||
| def _testJsonPostAllowsComments():
 | ||
|     print('testJsonPostAllowsComments')
 | ||
|     post_json_object = {
 | ||
|         "id": "123"
 | ||
|     }
 | ||
|     assert jsonPostAllowsComments(post_json_object)
 | ||
|     post_json_object = {
 | ||
|         "id": "123",
 | ||
|         "commentsEnabled": False
 | ||
|     }
 | ||
|     assert not jsonPostAllowsComments(post_json_object)
 | ||
|     post_json_object = {
 | ||
|         "id": "123",
 | ||
|         "rejectReplies": False
 | ||
|     }
 | ||
|     assert jsonPostAllowsComments(post_json_object)
 | ||
|     post_json_object = {
 | ||
|         "id": "123",
 | ||
|         "rejectReplies": True
 | ||
|     }
 | ||
|     assert not jsonPostAllowsComments(post_json_object)
 | ||
|     post_json_object = {
 | ||
|         "id": "123",
 | ||
|         "commentsEnabled": True
 | ||
|     }
 | ||
|     assert jsonPostAllowsComments(post_json_object)
 | ||
|     post_json_object = {
 | ||
|         "id": "123",
 | ||
|         "object": {
 | ||
|             "commentsEnabled": True
 | ||
|         }
 | ||
|     }
 | ||
|     assert jsonPostAllowsComments(post_json_object)
 | ||
|     post_json_object = {
 | ||
|         "id": "123",
 | ||
|         "object": {
 | ||
|             "commentsEnabled": False
 | ||
|         }
 | ||
|     }
 | ||
|     assert not jsonPostAllowsComments(post_json_object)
 | ||
| 
 | ||
| 
 | ||
| def _testRemoveIdEnding():
 | ||
|     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 _testValidContentWarning():
 | ||
|     print('testValidContentWarning')
 | ||
|     resultStr = validContentWarning('Valid content warning')
 | ||
|     assert resultStr == 'Valid content warning'
 | ||
| 
 | ||
|     resultStr = validContentWarning('Invalid #content warning')
 | ||
|     assert resultStr == 'Invalid content warning'
 | ||
| 
 | ||
|     resultStr = \
 | ||
|         validContentWarning('Invalid <a href="somesite">content warning</a>')
 | ||
|     assert resultStr == 'Invalid content warning'
 | ||
| 
 | ||
| 
 | ||
| def _testTranslations(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 _testConstantTimeStringCheck():
 | ||
|     print('testConstantTimeStringCheck')
 | ||
|     assert constantTimeStringCheck('testing', 'testing')
 | ||
|     assert not constantTimeStringCheck('testing', '1234')
 | ||
|     assert not constantTimeStringCheck('testing', '1234567')
 | ||
| 
 | ||
|     itterations = 256
 | ||
| 
 | ||
|     start = time.time()
 | ||
|     for timingTest in range(itterations):
 | ||
|         constantTimeStringCheck('nnjfbefefbsnjsdnvbcueftqfeuqfbqefnjeniwufgy',
 | ||
|                                 'nnjfbefefbsnjsdnvbcueftqfeuqfbqefnjeniwufgy')
 | ||
|     end = time.time()
 | ||
|     avTime1 = ((end - start) * 1000000 / itterations)
 | ||
| 
 | ||
|     # change a single character and observe timing difference
 | ||
|     start = time.time()
 | ||
|     for timingTest in range(itterations):
 | ||
|         constantTimeStringCheck('nnjfbefefbsnjsdnvbcueftqfeuqfbqefnjeniwufgy',
 | ||
|                                 'nnjfbefefbsnjsdnvbcueftqfeuqfbqeznjeniwufgy')
 | ||
|     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()
 | ||
|     for timingTest in range(itterations):
 | ||
|         constantTimeStringCheck('nnjfbefefbsnjsdnvbcueftqfeuqfbqefnjeniwufgy',
 | ||
|                                 'ano1befffbsn7sd3vbluef6qseuqfpqeznjgni9bfgi')
 | ||
|     end = time.time()
 | ||
|     avTime2 = ((end - start) * 1000000 / itterations)
 | ||
|     timeDiffMicroseconds = abs(avTime2 - avTime1)
 | ||
|     # time difference should be less than 10uS
 | ||
|     assert int(timeDiffMicroseconds) < 10
 | ||
| 
 | ||
| 
 | ||
| def _testReplaceEmailQuote():
 | ||
|     print('testReplaceEmailQuote')
 | ||
|     testStr = '<p>This content has no quote.</p>'
 | ||
|     assert htmlReplaceEmailQuote(testStr) == testStr
 | ||
| 
 | ||
|     testStr = '<p>This content has no quote.</p>' + \
 | ||
|         '<p>With multiple</p><p>lines</p>'
 | ||
|     assert htmlReplaceEmailQuote(testStr) == testStr
 | ||
| 
 | ||
|     testStr = '<p>"This is a quoted paragraph."</p>'
 | ||
|     assert htmlReplaceEmailQuote(testStr) == \
 | ||
|         '<p><blockquote>This is a quoted paragraph.</blockquote></p>'
 | ||
| 
 | ||
|     testStr = "<p><span class=\"h-card\">" + \
 | ||
|         "<a href=\"https://somewebsite/@nickname\" " + \
 | ||
|         "class=\"u-url mention\">@<span>nickname</span></a></span> " + \
 | ||
|         "<br />> This is a quote</p><p>Some other text.</p>"
 | ||
|     expectedStr = "<p><span class=\"h-card\">" + \
 | ||
|         "<a href=\"https://somewebsite/@nickname\" " + \
 | ||
|         "class=\"u-url mention\">@<span>nickname</span></a></span> " + \
 | ||
|         "<br /><blockquote>This is a quote</blockquote></p>" + \
 | ||
|         "<p>Some other text.</p>"
 | ||
|     resultStr = htmlReplaceEmailQuote(testStr)
 | ||
|     if resultStr != expectedStr:
 | ||
|         print('Result: ' + str(resultStr))
 | ||
|         print('Expect: ' + expectedStr)
 | ||
|     assert resultStr == expectedStr
 | ||
| 
 | ||
|     testStr = "<p>Some text:</p><p>> first line->second line</p>" + \
 | ||
|         "<p>Some question?</p>"
 | ||
|     expectedStr = "<p>Some text:</p><p><blockquote>first line-<br>" + \
 | ||
|         "second line</blockquote></p><p>Some question?</p>"
 | ||
|     resultStr = htmlReplaceEmailQuote(testStr)
 | ||
|     if resultStr != expectedStr:
 | ||
|         print('Result: ' + str(resultStr))
 | ||
|         print('Expect: ' + expectedStr)
 | ||
|     assert resultStr == expectedStr
 | ||
| 
 | ||
|     testStr = "<p><span class=\"h-card\">" + \
 | ||
|         "<a href=\"https://somedomain/@somenick\" " + \
 | ||
|         "class=\"u-url mention\">@<span>somenick</span>" + \
 | ||
|         "</a></span> </p><p>> Text1.<br />> <br />" + \
 | ||
|         "> Text2<br />> <br />> Text3<br />" + \
 | ||
|         "><br />> Text4<br />> <br />> " + \
 | ||
|         "Text5<br />> <br />> Text6</p><p>Text7</p>"
 | ||
|     expectedStr = "<p><span class=\"h-card\">" + \
 | ||
|         "<a href=\"https://somedomain/@somenick\" " + \
 | ||
|         "class=\"u-url mention\">@<span>somenick</span></a>" + \
 | ||
|         "</span> </p><p><blockquote> Text1.<br /><br />" + \
 | ||
|         "Text2<br /><br />Text3<br />><br />Text4<br />" + \
 | ||
|         "<br />Text5<br /><br />Text6</blockquote></p><p>Text7</p>"
 | ||
|     resultStr = htmlReplaceEmailQuote(testStr)
 | ||
|     if resultStr != expectedStr:
 | ||
|         print('Result: ' + str(resultStr))
 | ||
|         print('Expect: ' + expectedStr)
 | ||
|     assert resultStr == expectedStr
 | ||
| 
 | ||
| 
 | ||
| def _testRemoveHtmlTag():
 | ||
|     print('testRemoveHtmlTag')
 | ||
|     testStr = "<p><img width=\"864\" height=\"486\" " + \
 | ||
|         "src=\"https://somesiteorother.com/image.jpg\"></p>"
 | ||
|     resultStr = remove_htmlTag(testStr, 'width')
 | ||
|     assert resultStr == "<p><img height=\"486\" " + \
 | ||
|         "src=\"https://somesiteorother.com/image.jpg\"></p>"
 | ||
| 
 | ||
| 
 | ||
| def _testHashtagRuleTree():
 | ||
|     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 = hashtagRuleTree(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 = hashtagRuleTree(operators, conditionsStr,
 | ||
|                            tagsInConditions, moderated)
 | ||
|     assert str(tree) == str(['or', ['#foo'], ['#bar']])
 | ||
|     assert str(tagsInConditions) == str(['#foo', '#bar'])
 | ||
|     hashtags = ['#foo']
 | ||
|     assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
|     hashtags = ['#carrot', '#stick']
 | ||
|     assert not hashtagRuleResolve(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 = hashtagRuleTree(operators, conditionsStr,
 | ||
|                            tagsInConditions, moderated)
 | ||
|     assert str(tree) == str(['and', ['#foo'], ['from', ['"testsite.com"']]])
 | ||
|     assert str(tagsInConditions) == str(['#foo'])
 | ||
|     hashtags = ['#foo']
 | ||
|     assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
|     assert not hashtagRuleResolve(tree, hashtags, moderated, content,
 | ||
|                                   'othersite.net')
 | ||
| 
 | ||
|     content = 'This is a test'
 | ||
|     moderated = True
 | ||
|     conditionsStr = 'contains "is a" and #foo or #bar'
 | ||
|     tagsInConditions = []
 | ||
|     tree = hashtagRuleTree(operators, conditionsStr,
 | ||
|                            tagsInConditions, moderated)
 | ||
|     assert str(tree) == \
 | ||
|         str(['and', ['contains', ['"is a"']],
 | ||
|              ['or', ['#foo'], ['#bar']]])
 | ||
|     assert str(tagsInConditions) == str(['#foo', '#bar'])
 | ||
|     hashtags = ['#foo']
 | ||
|     assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
|     hashtags = ['#carrot', '#stick']
 | ||
|     assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
| 
 | ||
|     moderated = False
 | ||
|     conditionsStr = 'not moderated and #foo or #bar'
 | ||
|     tagsInConditions = []
 | ||
|     tree = hashtagRuleTree(operators, conditionsStr,
 | ||
|                            tagsInConditions, moderated)
 | ||
|     assert str(tree) == \
 | ||
|         str(['not', ['and', ['moderated'], ['or', ['#foo'], ['#bar']]]])
 | ||
|     assert str(tagsInConditions) == str(['#foo', '#bar'])
 | ||
|     hashtags = ['#foo']
 | ||
|     assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
|     hashtags = ['#carrot', '#stick']
 | ||
|     assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
| 
 | ||
|     moderated = True
 | ||
|     conditionsStr = 'moderated and #foo or #bar'
 | ||
|     tagsInConditions = []
 | ||
|     tree = hashtagRuleTree(operators, conditionsStr,
 | ||
|                            tagsInConditions, moderated)
 | ||
|     assert str(tree) == \
 | ||
|         str(['and', ['moderated'], ['or', ['#foo'], ['#bar']]])
 | ||
|     assert str(tagsInConditions) == str(['#foo', '#bar'])
 | ||
|     hashtags = ['#foo']
 | ||
|     assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
|     hashtags = ['#carrot', '#stick']
 | ||
|     assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
| 
 | ||
|     conditionsStr = 'x'
 | ||
|     tagsInConditions = []
 | ||
|     tree = hashtagRuleTree(operators, conditionsStr,
 | ||
|                            tagsInConditions, moderated)
 | ||
|     assert tree is None
 | ||
|     assert tagsInConditions == []
 | ||
|     hashtags = ['#foo']
 | ||
|     assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
| 
 | ||
|     conditionsStr = '#x'
 | ||
|     tagsInConditions = []
 | ||
|     tree = hashtagRuleTree(operators, conditionsStr,
 | ||
|                            tagsInConditions, moderated)
 | ||
|     assert str(tree) == str(['#x'])
 | ||
|     assert str(tagsInConditions) == str(['#x'])
 | ||
|     hashtags = ['#x']
 | ||
|     assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
|     hashtags = ['#y', '#z']
 | ||
|     assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
| 
 | ||
|     conditionsStr = 'not #b'
 | ||
|     tagsInConditions = []
 | ||
|     tree = hashtagRuleTree(operators, conditionsStr,
 | ||
|                            tagsInConditions, moderated)
 | ||
|     assert str(tree) == str(['not', ['#b']])
 | ||
|     assert str(tagsInConditions) == str(['#b'])
 | ||
|     hashtags = ['#y', '#z']
 | ||
|     assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
|     hashtags = ['#a', '#b', '#c']
 | ||
|     assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
| 
 | ||
|     conditionsStr = '#foo or #bar and #a'
 | ||
|     tagsInConditions = []
 | ||
|     tree = hashtagRuleTree(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 hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
|     hashtags = ['#bar', '#a']
 | ||
|     assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
|     hashtags = ['#foo', '#a']
 | ||
|     assert hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
|     hashtags = ['#x', '#a']
 | ||
|     assert not hashtagRuleResolve(tree, hashtags, moderated, content, url)
 | ||
| 
 | ||
| 
 | ||
| def _testGetNewswireTags():
 | ||
|     print('testGetNewswireTags')
 | ||
|     rssDescription = '<img src="https://somesite/someimage.jpg" ' + \
 | ||
|         'class="misc-stuff" alt="#ExcitingHashtag" ' + \
 | ||
|         'srcset="https://somesite/someimage.jpg" ' + \
 | ||
|         'sizes="(max-width: 864px) 100vw, 864px" />' + \
 | ||
|         'Compelling description with #ExcitingHashtag, which is ' + \
 | ||
|         'being posted in #BoringForum'
 | ||
|     tags = getNewswireTags(rssDescription, 10)
 | ||
|     assert len(tags) == 2
 | ||
|     assert '#BoringForum' in tags
 | ||
|     assert '#ExcitingHashtag' in tags
 | ||
| 
 | ||
| 
 | ||
| def _testFirstParagraphFromString():
 | ||
|     print('testFirstParagraphFromString')
 | ||
|     testStr = \
 | ||
|         '<p><a href="https://somesite.com/somepath">This is a test</a></p>' + \
 | ||
|         '<p>This is another paragraph</p>'
 | ||
|     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 _testParseFeedDate():
 | ||
|     print('testParseFeedDate')
 | ||
| 
 | ||
|     pubDate = "2020-12-14T00:08:06+00:00"
 | ||
|     publishedDate = parseFeedDate(pubDate)
 | ||
|     assert publishedDate == "2020-12-14 00:08:06+00:00"
 | ||
| 
 | ||
|     pubDate = "Tue, 08 Dec 2020 06:24:38 -0600"
 | ||
|     publishedDate = parseFeedDate(pubDate)
 | ||
|     assert publishedDate == "2020-12-08 12:24:38+00:00"
 | ||
| 
 | ||
|     pubDate = "2020-08-27T16:12:34+00:00"
 | ||
|     publishedDate = parseFeedDate(pubDate)
 | ||
|     assert publishedDate == "2020-08-27 16:12:34+00:00"
 | ||
| 
 | ||
|     pubDate = "Sun, 22 Nov 2020 19:51:33 +0100"
 | ||
|     publishedDate = parseFeedDate(pubDate)
 | ||
|     assert publishedDate == "2020-11-22 18:51:33+00:00"
 | ||
| 
 | ||
| 
 | ||
| def _testValidNickname():
 | ||
|     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 _testGuessHashtagCategory() -> None:
 | ||
|     print('testGuessHashtagCategory')
 | ||
|     hashtagCategories = {
 | ||
|         "foo": ["swan", "goose"],
 | ||
|         "bar": ["cats", "mouse"]
 | ||
|     }
 | ||
|     guess = guessHashtagCategory("unspecifiedgoose", hashtagCategories)
 | ||
|     assert guess == "foo"
 | ||
| 
 | ||
|     guess = guessHashtagCategory("mastocats", hashtagCategories)
 | ||
|     assert guess == "bar"
 | ||
| 
 | ||
| 
 | ||
| def _testGetMentionedPeople(base_dir: str) -> None:
 | ||
|     print('testGetMentionedPeople')
 | ||
|     content = "@dragon@cave.site @bat@cave.site This is a test."
 | ||
|     actors = getMentionedPeople(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 _testReplyToPublicPost(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 = \
 | ||
|         createPublicPost(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'] == \
 | ||
|         '<p><span class=\"h-card\">' + \
 | ||
|         '<a href=\"https://rat.site/@ninjarodent\" ' + \
 | ||
|         'class=\"u-url mention\">@<span>ninjarodent</span>' + \
 | ||
|         '</a></span> This is a test.</p>'
 | ||
|     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 _getFunctionCallArgs(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 getFunctionCalls(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 _functionArgsMatch(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 _moduleInGroups(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 _diagramGroups(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 _moduleInGroups(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 _moduleInGroups(modName, includeGroups, modGroups):
 | ||
|             continue
 | ||
|         if not modProperties.get('calls'):
 | ||
|             continue
 | ||
|         for modCall in modProperties['calls']:
 | ||
|             if modCall in excludeModulesFromDiagram:
 | ||
|                 continue
 | ||
|             if not _moduleInGroups(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 _testFunctions():
 | ||
|     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
 | ||
|             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'
 | ||
|     ]
 | ||
|     # 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 = \
 | ||
|                         _getFunctionCallArgs(name,
 | ||
|                                              modules[modName]['lines'],
 | ||
|                                              lineCtr)
 | ||
|                     funcArgs = functionProperties[name]['args']
 | ||
|                     if not _functionArgsMatch(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',
 | ||
|         '_sendToNamedAddresses',
 | ||
|         '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',
 | ||
|         'runInboxQueueWatchdog',
 | ||
|         'runInboxQueue',
 | ||
|         'runPostSchedule',
 | ||
|         'runPostScheduleWatchdog',
 | ||
|         'str2bool',
 | ||
|         'runNewswireDaemon',
 | ||
|         'runNewswireWatchdog',
 | ||
|         'runFederatedSharesWatchdog',
 | ||
|         'runFederatedSharesDaemon',
 | ||
|         'fitnessThread',
 | ||
|         'threadSendPost',
 | ||
|         'sendToFollowers',
 | ||
|         'expireCache',
 | ||
|         'getMutualsOfPerson',
 | ||
|         'runPostsQueue',
 | ||
|         'runSharesExpire',
 | ||
|         'runPostsWatchdog',
 | ||
|         'runSharesExpireWatchdog',
 | ||
|         'getThisWeeksEvents',
 | ||
|         'getAvailability',
 | ||
|         '_testThreadsFunction',
 | ||
|         'createServerGroup',
 | ||
|         'createServerAlice',
 | ||
|         'createServerBob',
 | ||
|         'createServerEve',
 | ||
|         'E2EEremoveDevice',
 | ||
|         '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 = \
 | ||
|                     getFunctionCalls(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
 | ||
| 
 | ||
|     _diagramGroups(['Commandline Interface', 'ActivityPub'], ['utils'],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
|     _diagramGroups(['Commandline Interface', 'Core'], ['utils'],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
|     _diagramGroups(['Timeline', 'Core'], ['utils'],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
|     _diagramGroups(['Web Interface', 'Core'], ['utils'],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
|     _diagramGroups(['Web Interface Columns', 'Core'], ['utils'],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
|     _diagramGroups(['Core'], [],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
|     _diagramGroups(['ActivityPub'], [],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
|     _diagramGroups(['ActivityPub', 'Core'], ['utils'],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
|     _diagramGroups(['ActivityPub', 'Security'], ['utils'],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
|     _diagramGroups(['Core', 'Security'], ['utils'],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
|     _diagramGroups(['Timeline', 'Security'], ['utils'],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
|     _diagramGroups(['Web Interface', 'Accessibility'],
 | ||
|                    ['utils', 'webapp_utils'],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
|     _diagramGroups(['Core', 'Accessibility'], ['utils'],
 | ||
|                    modules, modGroups, maxModuleCalls)
 | ||
| 
 | ||
| 
 | ||
| def _testLinksWithinPost(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 = \
 | ||
|         createPublicPost(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'] == \
 | ||
|         '<p>This is a test post with links.<br><br>' + \
 | ||
|         '<a href="ftp://ftp.ncdc.noaa.gov/pub/data/ghcn/v4/" ' + \
 | ||
|         'rel="nofollow noopener noreferrer" target="_blank">' + \
 | ||
|         '<span class="invisible">ftp://</span>' + \
 | ||
|         '<span class="ellipsis">' + \
 | ||
|         'ftp.ncdc.noaa.gov/pub/data/ghcn/v4/</span>' + \
 | ||
|         '</a><br><br><a href="https://libreserver.org" ' + \
 | ||
|         'rel="nofollow noopener noreferrer" target="_blank">' + \
 | ||
|         '<span class="invisible">https://</span>' + \
 | ||
|         '<span class="ellipsis">libreserver.org</span></a></p>'
 | ||
|     assert post_json_object['object']['content'] == \
 | ||
|         post_json_object['object']['contentMap'][system_language]
 | ||
| 
 | ||
|     content = "<p>Some text</p><p>Other text</p><p>More text</p>" + \
 | ||
|         "<pre><code>Errno::EOHNOES (No such file or rodent @ " + \
 | ||
|         "ik_right - /tmp/blah.png)<br></code></pre><p>" + \
 | ||
|         "(<a href=\"https://welllookeyhere.maam/error.txt\" " + \
 | ||
|         "rel=\"nofollow noopener noreferrer\" target=\"_blank\">" + \
 | ||
|         "wuh</a>)</p><p>Oh yeah like for sure</p>" + \
 | ||
|         "<p>Ground sloth tin opener</p>" + \
 | ||
|         "<p><a href=\"https://whocodedthis.huh/tags/" + \
 | ||
|         "taggedthing\" class=\"mention hashtag\" rel=\"tag\" " + \
 | ||
|         "target=\"_blank\">#<span>taggedthing</span></a></p>"
 | ||
|     post_json_object = \
 | ||
|         createPublicPost(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 _testMastoApi():
 | ||
|     print('testMastoApi')
 | ||
|     nickname = 'ThisIsATestNickname'
 | ||
|     mastoId = getMastoApiV1IdFromNickname(nickname)
 | ||
|     assert(mastoId)
 | ||
|     nickname2 = getNicknameFromMastoApiV1Id(mastoId)
 | ||
|     if nickname2 != nickname:
 | ||
|         print(nickname + ' != ' + nickname2)
 | ||
|     assert nickname2 == nickname
 | ||
| 
 | ||
| 
 | ||
| def _testDomainHandling():
 | ||
|     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 _testPrepareHtmlPostNickname():
 | ||
|     print('testPrepareHtmlPostNickname')
 | ||
|     postHtml = '<a class="imageAnchor" href="/users/bob?replyfollowers='
 | ||
|     postHtml += '<a class="imageAnchor" href="/users/bob?repeatprivate='
 | ||
|     result = prepareHtmlPostNickname('alice', postHtml)
 | ||
|     assert result == postHtml.replace('/bob?', '/alice?')
 | ||
| 
 | ||
|     postHtml = '<a class="imageAnchor" href="/users/bob?replyfollowers='
 | ||
|     postHtml += '<a class="imageAnchor" href="/users/bob;repeatprivate='
 | ||
|     expectedHtml = '<a class="imageAnchor" href="/users/alice?replyfollowers='
 | ||
|     expectedHtml += '<a class="imageAnchor" href="/users/bob;repeatprivate='
 | ||
|     result = prepareHtmlPostNickname('alice', postHtml)
 | ||
|     assert result == expectedHtml
 | ||
| 
 | ||
| 
 | ||
| def _testValidHashTag():
 | ||
|     print('testValidHashTag')
 | ||
|     assert validHashTag('ThisIsValid')
 | ||
|     assert validHashTag('ThisIsValid12345')
 | ||
|     assert validHashTag('ThisIsVälid')
 | ||
|     assert validHashTag('यहमान्यहै')
 | ||
|     assert not validHashTag('ThisIsNotValid!')
 | ||
|     assert not validHashTag('#ThisIsAlsoNotValid')
 | ||
|     assert not validHashTag('#यहमान्यहै')
 | ||
|     assert not validHashTag('ThisIsAlso&NotValid')
 | ||
|     assert not validHashTag('ThisIsAlsoNotValid"')
 | ||
|     assert not validHashTag('This Is Also Not Valid"')
 | ||
|     assert not validHashTag('This=IsAlsoNotValid"')
 | ||
| 
 | ||
| 
 | ||
| def _testMarkdownToHtml():
 | ||
|     print('testMarkdownToHtml')
 | ||
|     markdown = 'This is just plain text'
 | ||
|     assert markdownToHtml(markdown) == markdown
 | ||
| 
 | ||
|     markdown = 'This is a quotation:\n' + \
 | ||
|         '> Some quote or other'
 | ||
|     assert markdownToHtml(markdown) == 'This is a quotation:<br>' + \
 | ||
|         '<blockquote><i>Some quote or other</i></blockquote>'
 | ||
| 
 | ||
|     markdown = 'This is a multi-line quotation:\n' + \
 | ||
|         '> The first line\n' + \
 | ||
|         '> The second line'
 | ||
|     assert markdownToHtml(markdown) == \
 | ||
|         'This is a multi-line quotation:<br>' + \
 | ||
|         '<blockquote><i>The first line The second line</i></blockquote>'
 | ||
| 
 | ||
|     markdown = 'This is **bold**'
 | ||
|     assert markdownToHtml(markdown) == 'This is <b>bold</b>'
 | ||
| 
 | ||
|     markdown = 'This is *italic*'
 | ||
|     assert markdownToHtml(markdown) == 'This is <i>italic</i>'
 | ||
| 
 | ||
|     markdown = 'This is _underlined_'
 | ||
|     assert markdownToHtml(markdown) == 'This is <ul>underlined</ul>'
 | ||
| 
 | ||
|     markdown = 'This is **just** plain text'
 | ||
|     assert markdownToHtml(markdown) == 'This is <b>just</b> plain text'
 | ||
| 
 | ||
|     markdown = '# Title1\n### Title3\n## Title2\n'
 | ||
|     assert markdownToHtml(markdown) == \
 | ||
|         '<h1>Title1</h1><h3>Title3</h3><h2>Title2</h2>'
 | ||
| 
 | ||
|     markdown = \
 | ||
|         'This is [a link](https://something.somewhere) to something.\n' + \
 | ||
|         'And [something else](https://cat.pic).\n' + \
 | ||
|         'Or .'
 | ||
|     assert markdownToHtml(markdown) == \
 | ||
|         'This is <a href="https://something.somewhere" ' + \
 | ||
|         'target="_blank" rel="nofollow noopener noreferrer">' + \
 | ||
|         'a link</a> to something.<br>' + \
 | ||
|         'And <a href="https://cat.pic" ' + \
 | ||
|         'target="_blank" rel="nofollow noopener noreferrer">' + \
 | ||
|         'something else</a>.<br>' + \
 | ||
|         'Or <img class="markdownImage" src="/cat.jpg" alt="pounce" />.'
 | ||
| 
 | ||
| 
 | ||
| def _testExtractTextFieldsInPOST():
 | ||
|     print('testExtractTextFieldsInPOST')
 | ||
|     boundary = '-----------------------------116202748023898664511855843036'
 | ||
|     formData = '-----------------------------116202748023898664511855' + \
 | ||
|         '843036\r\nContent-Disposition: form-data; name="submitPost"' + \
 | ||
|         '\r\n\r\nSubmit\r\n-----------------------------116202748023' + \
 | ||
|         '898664511855843036\r\nContent-Disposition: form-data; name=' + \
 | ||
|         '"subject"\r\n\r\n\r\n-----------------------------116202748' + \
 | ||
|         '023898664511855843036\r\nContent-Disposition: form-data; na' + \
 | ||
|         'me="message"\r\n\r\nThis is a ; test\r\n-------------------' + \
 | ||
|         '----------116202748023898664511855843036\r\nContent-Disposi' + \
 | ||
|         'tion: form-data; name="commentsEnabled"\r\n\r\non\r\n------' + \
 | ||
|         '-----------------------116202748023898664511855843036\r\nCo' + \
 | ||
|         'ntent-Disposition: form-data; name="eventDate"\r\n\r\n\r\n' + \
 | ||
|         '-----------------------------116202748023898664511855843036' + \
 | ||
|         '\r\nContent-Disposition: form-data; name="eventTime"\r\n\r' + \
 | ||
|         '\n\r\n-----------------------------116202748023898664511855' + \
 | ||
|         '843036\r\nContent-Disposition: form-data; name="location"' + \
 | ||
|         '\r\n\r\n\r\n-----------------------------116202748023898664' + \
 | ||
|         '511855843036\r\nContent-Disposition: form-data; name=' + \
 | ||
|         '"imageDescription"\r\n\r\n\r\n-----------------------------' + \
 | ||
|         '116202748023898664511855843036\r\nContent-Disposition: ' + \
 | ||
|         'form-data; name="attachpic"; filename=""\r\nContent-Type: ' + \
 | ||
|         'application/octet-stream\r\n\r\n\r\n----------------------' + \
 | ||
|         '-------116202748023898664511855843036--\r\n'
 | ||
|     debug = False
 | ||
|     fields = extractTextFieldsInPOST(None, boundary, debug, formData)
 | ||
|     assert fields['submitPost'] == 'Submit'
 | ||
|     assert fields['subject'] == ''
 | ||
|     assert fields['commentsEnabled'] == 'on'
 | ||
|     assert fields['eventDate'] == ''
 | ||
|     assert fields['eventTime'] == ''
 | ||
|     assert fields['location'] == ''
 | ||
|     assert fields['imageDescription'] == ''
 | ||
|     assert fields['message'] == 'This is a ; test'
 | ||
| 
 | ||
| 
 | ||
| def _testSpeakerReplaceLinks():
 | ||
|     print('testSpeakerReplaceLinks')
 | ||
|     text = 'The Tor Project: For Snowflake volunteers: If you use ' + \
 | ||
|         'Firefox, Brave, or Chrome, our Snowflake extension turns ' + \
 | ||
|         'your browser into a proxy that connects Tor users in ' + \
 | ||
|         'censored regions to the Tor network. Note: you should ' + \
 | ||
|         'not run more than one snowflake in the same ' + \
 | ||
|         'network.https://support.torproject.org/censorship/' + \
 | ||
|         'how-to-help-running-snowflake/'
 | ||
|     detectedLinks = []
 | ||
|     result = speakerReplaceLinks(text, {'Linked': 'Web link'}, detectedLinks)
 | ||
|     assert len(detectedLinks) == 1
 | ||
|     assert detectedLinks[0] == \
 | ||
|         'https://support.torproject.org/censorship/' + \
 | ||
|         'how-to-help-running-snowflake/'
 | ||
|     assert 'Web link support.torproject.org' in result
 | ||
| 
 | ||
| 
 | ||
| def _testCamelCaseSplit():
 | ||
|     print('testCamelCaseSplit')
 | ||
|     testStr = 'ThisIsCamelCase'
 | ||
|     assert camel_case_split(testStr) == 'This Is Camel Case'
 | ||
| 
 | ||
|     testStr = 'Notcamelcase test'
 | ||
|     assert camel_case_split(testStr) == 'Notcamelcase test'
 | ||
| 
 | ||
| 
 | ||
| def _testEmojiImages():
 | ||
|     print('testEmojiImages')
 | ||
|     emojiFilename = 'emoji/default_emoji.json'
 | ||
|     assert os.path.isfile(emojiFilename)
 | ||
|     emojiJson = load_json(emojiFilename)
 | ||
|     assert emojiJson
 | ||
|     for emojiName, emojiImage in emojiJson.items():
 | ||
|         emojiImageFilename = 'emoji/' + emojiImage + '.png'
 | ||
|         if not os.path.isfile(emojiImageFilename):
 | ||
|             print('Missing emoji image ' + emojiName + ' ' +
 | ||
|                   emojiImage + '.png')
 | ||
|         assert os.path.isfile(emojiImageFilename)
 | ||
| 
 | ||
| 
 | ||
| def _testExtractPGPPublicKey():
 | ||
|     print('testExtractPGPPublicKey')
 | ||
|     pubKey = \
 | ||
|         '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n' + \
 | ||
|         'mDMEWZBueBYJKwYBBAHaRw8BAQdAKx1t6wL0RTuU6/' + \
 | ||
|         'IBjngMbVJJ3Wg/3UW73/PV\n' + \
 | ||
|         'I47xKTS0IUJvYiBNb3R0cmFtIDxib2JAZnJlZWRvb' + \
 | ||
|         'WJvbmUubmV0PoiQBBMWCAA4\n' + \
 | ||
|         'FiEEmruCwAq/OfgmgEh9zCU2GR+nwz8FAlmQbngCG' + \
 | ||
|         'wMFCwkIBwMFFQoJCAsFFgID\n' + \
 | ||
|         'AQACHgECF4AACgkQzCU2GR+nwz/9sAD/YgsHnVszH' + \
 | ||
|         'Nz1zlVc5EgY1ByDupiJpHj0\n' + \
 | ||
|         'XsLYk3AbNRgBALn45RqgD4eWHpmOriH09H5Rc5V9i' + \
 | ||
|         'N4+OiGUn2AzJ6oHuDgEWZBu\n' + \
 | ||
|         'eBIKKwYBBAGXVQEFAQEHQPRBG2ZQJce475S3e0Dxe' + \
 | ||
|         'b0Fz5WdEu2q3GYLo4QG+4Ry\n' + \
 | ||
|         'AwEIB4h4BBgWCAAgFiEEmruCwAq/OfgmgEh9zCU2G' + \
 | ||
|         'R+nwz8FAlmQbngCGwwACgkQ\n' + \
 | ||
|         'zCU2GR+nwz+OswD+JOoyBku9FzuWoVoOevU2HH+bP' + \
 | ||
|         'OMDgY2OLnST9ZSyHkMBAMcK\n' + \
 | ||
|         'fnaZ2Wi050483Sj2RmQRpb99Dod7rVZTDtCqXk0J\n' + \
 | ||
|         '=gv5G\n' + \
 | ||
|         '-----END PGP PUBLIC KEY BLOCK-----'
 | ||
|     testStr = "Some introduction\n\n" + pubKey + "\n\nSome message."
 | ||
|     assert contains_pgp_public_key(testStr)
 | ||
|     assert not contains_pgp_public_key('String without a pgp key')
 | ||
|     result = extractPGPPublicKey(testStr)
 | ||
|     assert result
 | ||
|     assert result == pubKey
 | ||
| 
 | ||
| 
 | ||
| def testUpdateActor(base_dir: str):
 | ||
|     print('Testing update of actor properties')
 | ||
| 
 | ||
|     global testServerAliceRunning
 | ||
|     testServerAliceRunning = False
 | ||
| 
 | ||
|     http_prefix = 'http'
 | ||
|     proxy_type = None
 | ||
|     federation_list = []
 | ||
| 
 | ||
|     if os.path.isdir(base_dir + '/.tests'):
 | ||
|         shutil.rmtree(base_dir + '/.tests',
 | ||
|                       ignore_errors=False, onerror=None)
 | ||
|     os.mkdir(base_dir + '/.tests')
 | ||
| 
 | ||
|     # create the server
 | ||
|     aliceDir = base_dir + '/.tests/alice'
 | ||
|     aliceDomain = '127.0.0.11'
 | ||
|     alicePort = 61792
 | ||
|     aliceSendThreads = []
 | ||
|     bobAddress = '127.0.0.84:6384'
 | ||
| 
 | ||
|     global thrAlice
 | ||
|     if thrAlice:
 | ||
|         while thrAlice.is_alive():
 | ||
|             thrAlice.stop()
 | ||
|             time.sleep(1)
 | ||
|         thrAlice.kill()
 | ||
| 
 | ||
|     thrAlice = \
 | ||
|         threadWithTrace(target=createServerAlice,
 | ||
|                         args=(aliceDir, aliceDomain, alicePort, bobAddress,
 | ||
|                               federation_list, False, False,
 | ||
|                               aliceSendThreads),
 | ||
|                         daemon=True)
 | ||
| 
 | ||
|     thrAlice.start()
 | ||
|     assert thrAlice.is_alive() is True
 | ||
| 
 | ||
|     # wait for server to be running
 | ||
|     ctr = 0
 | ||
|     while not testServerAliceRunning:
 | ||
|         time.sleep(1)
 | ||
|         ctr += 1
 | ||
|         if ctr > 60:
 | ||
|             break
 | ||
|     print('Alice online: ' + str(testServerAliceRunning))
 | ||
| 
 | ||
|     print('\n\n*******************************************************')
 | ||
|     print('Alice updates her PGP key')
 | ||
| 
 | ||
|     sessionAlice = createSession(proxy_type)
 | ||
|     cached_webfingers = {}
 | ||
|     person_cache = {}
 | ||
|     password = 'alicepass'
 | ||
|     outboxPath = aliceDir + '/accounts/alice@' + aliceDomain + '/outbox'
 | ||
|     actorFilename = aliceDir + '/accounts/' + 'alice@' + aliceDomain + '.json'
 | ||
|     assert os.path.isfile(actorFilename)
 | ||
|     assert len([name for name in os.listdir(outboxPath)
 | ||
|                 if os.path.isfile(os.path.join(outboxPath, name))]) == 0
 | ||
|     pubKey = \
 | ||
|         '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\n' + \
 | ||
|         'mDMEWZBueBYJKwYBBAHaRw8BAQdAKx1t6wL0RTuU6/' + \
 | ||
|         'IBjngMbVJJ3Wg/3UW73/PV\n' + \
 | ||
|         'I47xKTS0IUJvYiBNb3R0cmFtIDxib2JAZnJlZWRvb' + \
 | ||
|         'WJvbmUubmV0PoiQBBMWCAA4\n' + \
 | ||
|         'FiEEmruCwAq/OfgmgEh9zCU2GR+nwz8FAlmQbngCG' + \
 | ||
|         'wMFCwkIBwMFFQoJCAsFFgID\n' + \
 | ||
|         'AQACHgECF4AACgkQzCU2GR+nwz/9sAD/YgsHnVszH' + \
 | ||
|         'Nz1zlVc5EgY1ByDupiJpHj0\n' + \
 | ||
|         'XsLYk3AbNRgBALn45RqgD4eWHpmOriH09H5Rc5V9i' + \
 | ||
|         'N4+OiGUn2AzJ6oHuDgEWZBu\n' + \
 | ||
|         'eBIKKwYBBAGXVQEFAQEHQPRBG2ZQJce475S3e0Dxe' + \
 | ||
|         'b0Fz5WdEu2q3GYLo4QG+4Ry\n' + \
 | ||
|         'AwEIB4h4BBgWCAAgFiEEmruCwAq/OfgmgEh9zCU2G' + \
 | ||
|         'R+nwz8FAlmQbngCGwwACgkQ\n' + \
 | ||
|         'zCU2GR+nwz+OswD+JOoyBku9FzuWoVoOevU2HH+bP' + \
 | ||
|         'OMDgY2OLnST9ZSyHkMBAMcK\n' + \
 | ||
|         'fnaZ2Wi050483Sj2RmQRpb99Dod7rVZTDtCqXk0J\n' + \
 | ||
|         '=gv5G\n' + \
 | ||
|         '-----END PGP PUBLIC KEY BLOCK-----'
 | ||
|     signing_priv_key_pem = None
 | ||
|     actorUpdate = \
 | ||
|         pgpPublicKeyUpload(aliceDir, sessionAlice,
 | ||
|                            'alice', password,
 | ||
|                            aliceDomain, alicePort,
 | ||
|                            http_prefix,
 | ||
|                            cached_webfingers, person_cache,
 | ||
|                            True, pubKey, signing_priv_key_pem)
 | ||
|     print('actor update result: ' + str(actorUpdate))
 | ||
|     assert actorUpdate
 | ||
| 
 | ||
|     # load alice actor
 | ||
|     print('Loading actor: ' + actorFilename)
 | ||
|     actor_json = load_json(actorFilename)
 | ||
|     assert actor_json
 | ||
|     if len(actor_json['attachment']) == 0:
 | ||
|         print("actor_json['attachment'] has no contents")
 | ||
|     assert len(actor_json['attachment']) > 0
 | ||
|     propertyFound = False
 | ||
|     for property_value in actor_json['attachment']:
 | ||
|         if property_value['name'] == 'PGP':
 | ||
|             print('PGP property set within attachment')
 | ||
|             assert pubKey in property_value['value']
 | ||
|             propertyFound = True
 | ||
|     assert propertyFound
 | ||
| 
 | ||
|     # stop the server
 | ||
|     thrAlice.kill()
 | ||
|     thrAlice.join()
 | ||
|     assert thrAlice.is_alive() is False
 | ||
| 
 | ||
|     os.chdir(base_dir)
 | ||
|     if os.path.isdir(base_dir + '/.tests'):
 | ||
|         shutil.rmtree(base_dir + '/.tests', ignore_errors=False, onerror=None)
 | ||
| 
 | ||
| 
 | ||
| def _testRemovePostInteractions() -> None:
 | ||
|     print('testRemovePostInteractions')
 | ||
|     post_json_object = {
 | ||
|         "type": "Create",
 | ||
|         "object": {
 | ||
|             "to": ["#Public"],
 | ||
|             "likes": {
 | ||
|                 "items": ["a", "b", "c"]
 | ||
|             },
 | ||
|             "replies": {
 | ||
|                 "replyStuff": ["a", "b", "c"]
 | ||
|             },
 | ||
|             "shares": {
 | ||
|                 "sharesStuff": ["a", "b", "c"]
 | ||
|             },
 | ||
|             "bookmarks": {
 | ||
|                 "bookmarksStuff": ["a", "b", "c"]
 | ||
|             },
 | ||
|             "ignores": {
 | ||
|                 "ignoresStuff": ["a", "b", "c"]
 | ||
|             }
 | ||
|         }
 | ||
|     }
 | ||
|     removePostInteractions(post_json_object, True)
 | ||
|     assert post_json_object['object']['likes']['items'] == []
 | ||
|     assert post_json_object['object']['replies'] == {}
 | ||
|     assert post_json_object['object']['shares'] == {}
 | ||
|     assert post_json_object['object']['bookmarks'] == {}
 | ||
|     assert post_json_object['object']['ignores'] == {}
 | ||
|     post_json_object['object']['to'] = ["some private address"]
 | ||
|     assert not removePostInteractions(post_json_object, False)
 | ||
| 
 | ||
| 
 | ||
| def _testSpoofGeolocation() -> None:
 | ||
|     print('testSpoofGeolocation')
 | ||
|     nogoLine = \
 | ||
|         'NEW YORK, USA: 73.951W,40.879,  73.974W,40.83,  ' + \
 | ||
|         '74.029W,40.756,  74.038W,40.713,  74.056W,40.713,  ' + \
 | ||
|         '74.127W,40.647,  74.038W,40.629,  73.995W,40.667,  ' + \
 | ||
|         '74.014W,40.676,  73.994W,40.702,  73.967W,40.699,  ' + \
 | ||
|         '73.958W,40.729,  73.956W,40.745,  73.918W,40.781,  ' + \
 | ||
|         '73.937W,40.793,  73.946W,40.782,  73.977W,40.738,  ' + \
 | ||
|         '73.98W,40.713,  74.012W,40.705,  74.006W,40.752,  ' + \
 | ||
|         '73.955W,40.824'
 | ||
|     polygon = parseNogoString(nogoLine)
 | ||
|     assert len(polygon) > 0
 | ||
|     assert polygon[0][1] == -73.951
 | ||
|     assert polygon[0][0] == 40.879
 | ||
|     citiesList = [
 | ||
|         'NEW YORK, USA:40.7127281:W74.0060152:784',
 | ||
|         'LOS ANGELES, USA:34.0536909:W118.242766:1214',
 | ||
|         'SAN FRANCISCO, USA:37.74594738515095:W122.44299445520019:121',
 | ||
|         'HOUSTON, USA:29.6072:W95.1586:1553',
 | ||
|         'MANCHESTER, ENGLAND:53.4794892:W2.2451148:1276',
 | ||
|         'BERLIN, GERMANY:52.5170365:13.3888599:891',
 | ||
|         'ANKARA, TURKEY:39.93:32.85:24521',
 | ||
|         'LONDON, ENGLAND:51.5073219:W0.1276474:1738',
 | ||
|         'SEATTLE, USA:47.59840153253106:W122.31143714060059:217'
 | ||
|     ]
 | ||
|     testSquare = [
 | ||
|         [[0.03, 0.01], [0.02, 10], [10.01, 10.02], [10.03, 0.02]]
 | ||
|     ]
 | ||
|     assert pointInNogo(testSquare, 5, 5)
 | ||
|     assert pointInNogo(testSquare, 2, 3)
 | ||
|     assert not pointInNogo(testSquare, 20, 5)
 | ||
|     assert not pointInNogo(testSquare, 11, 6)
 | ||
|     assert not pointInNogo(testSquare, 5, -5)
 | ||
|     assert not pointInNogo(testSquare, 5, 11)
 | ||
|     assert not pointInNogo(testSquare, -5, -5)
 | ||
|     assert not pointInNogo(testSquare, -5, 5)
 | ||
|     nogoList = []
 | ||
|     curr_time = datetime.datetime.utcnow()
 | ||
|     decoySeed = 7634681
 | ||
|     cityRadius = 0.1
 | ||
|     coords = spoofGeolocation('', 'los angeles', curr_time,
 | ||
|                               decoySeed, citiesList, nogoList)
 | ||
|     assert coords[0] >= 34.0536909 - cityRadius
 | ||
|     assert coords[0] <= 34.0536909 + cityRadius
 | ||
|     assert coords[1] >= 118.242766 - cityRadius
 | ||
|     assert coords[1] <= 118.242766 + cityRadius
 | ||
|     assert coords[2] == 'N'
 | ||
|     assert coords[3] == 'W'
 | ||
|     assert len(coords[4]) > 4
 | ||
|     assert len(coords[5]) > 4
 | ||
|     assert coords[6] > 0
 | ||
|     nogoList = []
 | ||
|     coords = spoofGeolocation('', 'unknown', curr_time,
 | ||
|                               decoySeed, citiesList, nogoList)
 | ||
|     assert coords[0] >= 51.8744 - cityRadius
 | ||
|     assert coords[0] <= 51.8744 + cityRadius
 | ||
|     assert coords[1] >= 0.368333 - cityRadius
 | ||
|     assert coords[1] <= 0.368333 + cityRadius
 | ||
|     assert coords[2] == 'N'
 | ||
|     assert coords[3] == 'W'
 | ||
|     assert len(coords[4]) == 0
 | ||
|     assert len(coords[5]) == 0
 | ||
|     assert coords[6] == 0
 | ||
|     kmlStr = '<?xml version="1.0" encoding="UTF-8"?>\n'
 | ||
|     kmlStr += '<kml xmlns="http://www.opengis.net/kml/2.2">\n'
 | ||
|     kmlStr += '<Document>\n'
 | ||
|     nogoLine2 = \
 | ||
|         'NEW YORK, USA: 74.115W,40.663,  74.065W,40.602,  ' + \
 | ||
|         '74.118W,40.555,  74.047W,40.516,  73.882W,40.547,  ' + \
 | ||
|         '73.909W,40.618,  73.978W,40.579,  74.009W,40.602,  ' + \
 | ||
|         '74.033W,40.61,  74.039W,40.623,  74.032W,40.641,  ' + \
 | ||
|         '73.996W,40.665'
 | ||
|     polygon2 = parseNogoString(nogoLine2)
 | ||
|     nogoList = [polygon, polygon2]
 | ||
|     for i in range(1000):
 | ||
|         dayNumber = randint(10, 30)
 | ||
|         hour = randint(1, 23)
 | ||
|         hourStr = str(hour)
 | ||
|         if hour < 10:
 | ||
|             hourStr = '0' + hourStr
 | ||
|         dateTimeStr = "2021-05-" + str(dayNumber) + " " + hourStr + ":14"
 | ||
|         curr_time = datetime.datetime.strptime(dateTimeStr, "%Y-%m-%d %H:%M")
 | ||
|         coords = spoofGeolocation('', 'new york, usa', curr_time,
 | ||
|                                   decoySeed, citiesList, nogoList)
 | ||
|         longitude = coords[1]
 | ||
|         if coords[3] == 'W':
 | ||
|             longitude = -coords[1]
 | ||
|         kmlStr += '<Placemark id="' + str(i) + '">\n'
 | ||
|         kmlStr += '  <name>' + str(i) + '</name>\n'
 | ||
|         kmlStr += '  <Point>\n'
 | ||
|         kmlStr += '    <coordinates>' + str(longitude) + ',' + \
 | ||
|             str(coords[0]) + ',0</coordinates>\n'
 | ||
|         kmlStr += '  </Point>\n'
 | ||
|         kmlStr += '</Placemark>\n'
 | ||
| 
 | ||
|     nogoLine = \
 | ||
|         'LONDON, ENGLAND: 0.23888E,51.459,  0.1216E,51.5,  ' + \
 | ||
|         '0.016E,51.479,  0.097W,51.502,  0.126W,51.482,  ' + \
 | ||
|         '0.196W,51.457,  0.292W,51.465,  0.309W,51.49,  ' + \
 | ||
|         '0.226W,51.495,  0.198W,51.47,  0.174W,51.488,  ' + \
 | ||
|         '0.136W,51.489,  0.1189W,51.515,  0.038E,51.513,  ' + \
 | ||
|         '0.0692E,51.51,  0.12833E,51.526,  0.3289E,51.475'
 | ||
|     polygon = parseNogoString(nogoLine)
 | ||
|     nogoLine2 = \
 | ||
|         'LONDON, ENGLAND: 0.054W,51.535,  0.044W,51.53,  ' + \
 | ||
|         '0.008W,51.55,  0.0429W,51.57,  0.038W,51.6,  ' + \
 | ||
|         '0.0209W,51.603,  0.032W,51.613,  0.00191E,51.66,  ' + \
 | ||
|         '0.024W,51.666,  0.0313W,51.659,  0.0639W,51.579,  ' + \
 | ||
|         '0.059W,51.568,  0.0329W,51.552'
 | ||
|     polygon2 = parseNogoString(nogoLine2)
 | ||
|     nogoList = [polygon, polygon2]
 | ||
|     for i in range(1000):
 | ||
|         dayNumber = randint(10, 30)
 | ||
|         hour = randint(1, 23)
 | ||
|         hourStr = str(hour)
 | ||
|         if hour < 10:
 | ||
|             hourStr = '0' + hourStr
 | ||
|         dateTimeStr = "2021-05-" + str(dayNumber) + " " + hourStr + ":14"
 | ||
|         curr_time = datetime.datetime.strptime(dateTimeStr, "%Y-%m-%d %H:%M")
 | ||
|         coords = spoofGeolocation('', 'london, england', curr_time,
 | ||
|                                   decoySeed, citiesList, nogoList)
 | ||
|         longitude = coords[1]
 | ||
|         if coords[3] == 'W':
 | ||
|             longitude = -coords[1]
 | ||
|         kmlStr += '<Placemark id="' + str(i) + '">\n'
 | ||
|         kmlStr += '  <name>' + str(i) + '</name>\n'
 | ||
|         kmlStr += '  <Point>\n'
 | ||
|         kmlStr += '    <coordinates>' + str(longitude) + ',' + \
 | ||
|             str(coords[0]) + ',0</coordinates>\n'
 | ||
|         kmlStr += '  </Point>\n'
 | ||
|         kmlStr += '</Placemark>\n'
 | ||
| 
 | ||
|     nogoLine = \
 | ||
|         'SAN FRANCISCO, USA: 121.988W,37.408,  121.924W,37.452,  ' + \
 | ||
|         '121.951W,37.498,  121.992W,37.505,  122.056W,37.54,  ' + \
 | ||
|         '122.077W,37.578,  122.098W,37.618,  122.131W,37.637,  ' + \
 | ||
|         '122.189W,37.706,  122.227W,37.775,  122.279W,37.798,  ' + \
 | ||
|         '122.315W,37.802,  122.291W,37.832,  122.309W,37.902,  ' + \
 | ||
|         '122.382W,37.915,  122.368W,37.927,  122.514W,37.882,  ' + \
 | ||
|         '122.473W,37.83,  122.481W,37.788,  122.394W,37.796,  ' + \
 | ||
|         '122.384W,37.729,  122.4W,37.688,  122.382W,37.654,  ' + \
 | ||
|         '122.406W,37.637,  122.392W,37.612,  122.356W,37.586,  ' + \
 | ||
|         '122.332W,37.586,  122.275W,37.529,  122.228W,37.488,  ' + \
 | ||
|         '122.181W,37.482,  122.134W,37.48,  122.128W,37.471,  ' + \
 | ||
|         '122.122W,37.448,  122.095W,37.428,  122.07W,37.413,  ' + \
 | ||
|         '122.036W,37.402,  122.035W,37.421'
 | ||
|     polygon = parseNogoString(nogoLine)
 | ||
|     nogoLine2 = \
 | ||
|         'SAN FRANCISCO, USA: 122.446W,37.794,  122.511W,37.778,  ' + \
 | ||
|         '122.51W,37.771,  122.454W,37.775,  122.452W,37.766,  ' + \
 | ||
|         '122.510W,37.763,  122.506W,37.735,  122.498W,37.733,  ' + \
 | ||
|         '122.496W,37.729,  122.491W,37.729,  122.475W,37.73,  ' + \
 | ||
|         '122.474W,37.72,  122.484W,37.72,  122.485W,37.703,  ' + \
 | ||
|         '122.495W,37.702,  122.493W,37.679,  122.486W,37.667,  ' + \
 | ||
|         '122.492W,37.664,  122.493W,37.629,  122.456W,37.625,  ' + \
 | ||
|         '122.450W,37.617,  122.455W,37.621,  122.41W,37.586,  ' + \
 | ||
|         '122.383W,37.561,  122.335W,37.509,  122.655W,37.48,  ' + \
 | ||
|         '122.67W,37.9,  122.272W,37.93,  122.294W,37.801,  ' + \
 | ||
|         '122.448W,37.804'
 | ||
|     polygon2 = parseNogoString(nogoLine2)
 | ||
|     nogoList = [polygon, polygon2]
 | ||
|     for i in range(1000):
 | ||
|         dayNumber = randint(10, 30)
 | ||
|         hour = randint(1, 23)
 | ||
|         hourStr = str(hour)
 | ||
|         if hour < 10:
 | ||
|             hourStr = '0' + hourStr
 | ||
|         dateTimeStr = "2021-05-" + str(dayNumber) + " " + hourStr + ":14"
 | ||
|         curr_time = datetime.datetime.strptime(dateTimeStr, "%Y-%m-%d %H:%M")
 | ||
|         coords = spoofGeolocation('', 'SAN FRANCISCO, USA', curr_time,
 | ||
|                                   decoySeed, citiesList, nogoList)
 | ||
|         longitude = coords[1]
 | ||
|         if coords[3] == 'W':
 | ||
|             longitude = -coords[1]
 | ||
|         kmlStr += '<Placemark id="' + str(i) + '">\n'
 | ||
|         kmlStr += '  <name>' + str(i) + '</name>\n'
 | ||
|         kmlStr += '  <Point>\n'
 | ||
|         kmlStr += '    <coordinates>' + str(longitude) + ',' + \
 | ||
|             str(coords[0]) + ',0</coordinates>\n'
 | ||
|         kmlStr += '  </Point>\n'
 | ||
|         kmlStr += '</Placemark>\n'
 | ||
| 
 | ||
|     nogoLine = \
 | ||
|         'SEATTLE, USA: 122.247W,47.918,  122.39W,47.802,  ' + \
 | ||
|         '122.389W,47.769,  122.377W,47.758,  122.371W,47.726,  ' + \
 | ||
|         '122.379W,47.706,  122.4W,47.696,  122.405W,47.673,  ' + \
 | ||
|         '122.416W,47.65,  122.414W,47.642,  122.391W,47.632,  ' + \
 | ||
|         '122.373W,47.633,  122.336W,47.602,  122.288W,47.501,  ' + \
 | ||
|         '122.299W,47.503,  122.386W,47.592,  122.412W,47.574,  ' + \
 | ||
|         '122.394W,47.549,  122.388W,47.507,  122.35W,47.481,  ' + \
 | ||
|         '122.365W,47.459,  122.33W,47.406,  122.323W,47.392,  ' + \
 | ||
|         '122.321W,47.346,  122.441W,47.302,  122.696W,47.085,  ' + \
 | ||
|         '122.926W,47.066,  122.929W,48.383'
 | ||
|     polygon = parseNogoString(nogoLine)
 | ||
|     nogoLine2 = \
 | ||
|         'SEATTLE, USA: 122.267W,47.758,  122.29W,47.471,  ' + \
 | ||
|         '122.272W,47.693,  122.256W,47.672,  122.278W,47.652,  ' + \
 | ||
|         '122.29W,47.583,  122.262W,47.548,  122.265W,47.52,  ' + \
 | ||
|         '122.218W,47.498,  122.194W,47.501,  122.193W,47.55,  ' + \
 | ||
|         '122.173W,47.58,  122.22W,47.617,  122.238W,47.617,  ' + \
 | ||
|         '122.239W,47.637,  122.2W,47.644,  122.207W,47.703,  ' + \
 | ||
|         '122.22W,47.705,  122.231W,47.699,  122.255W,47.751'
 | ||
|     polygon2 = parseNogoString(nogoLine2)
 | ||
|     nogoLine3 = \
 | ||
|         'SEATTLE, USA: 122.347W,47.675,  122.344W,47.681,  ' + \
 | ||
|         '122.337W,47.685,  122.324W,47.679,  122.331W,47.677,  ' + \
 | ||
|         '122.34W,47.669,  122.34W,47.664,  122.348W,47.665'
 | ||
|     polygon3 = parseNogoString(nogoLine3)
 | ||
|     nogoLine4 = \
 | ||
|         'SEATTLE, USA: 122.423W,47.669,  122.345W,47.641,  ' + \
 | ||
|         '122.34W,47.625,  122.327W,47.626,  122.274W,47.64,  ' + \
 | ||
|         '122.268W,47.654,  122.327W,47.654,  122.336W,47.647,  ' + \
 | ||
|         '122.429W,47.684'
 | ||
|     polygon4 = parseNogoString(nogoLine4)
 | ||
|     nogoList = [polygon, polygon2, polygon3, polygon4]
 | ||
|     for i in range(1000):
 | ||
|         dayNumber = randint(10, 30)
 | ||
|         hour = randint(1, 23)
 | ||
|         hourStr = str(hour)
 | ||
|         if hour < 10:
 | ||
|             hourStr = '0' + hourStr
 | ||
|         dateTimeStr = "2021-05-" + str(dayNumber) + " " + hourStr + ":14"
 | ||
|         curr_time = datetime.datetime.strptime(dateTimeStr, "%Y-%m-%d %H:%M")
 | ||
|         coords = spoofGeolocation('', 'SEATTLE, USA', curr_time,
 | ||
|                                   decoySeed, citiesList, nogoList)
 | ||
|         longitude = coords[1]
 | ||
|         if coords[3] == 'W':
 | ||
|             longitude = -coords[1]
 | ||
|         kmlStr += '<Placemark id="' + str(i) + '">\n'
 | ||
|         kmlStr += '  <name>' + str(i) + '</name>\n'
 | ||
|         kmlStr += '  <Point>\n'
 | ||
|         kmlStr += '    <coordinates>' + str(longitude) + ',' + \
 | ||
|             str(coords[0]) + ',0</coordinates>\n'
 | ||
|         kmlStr += '  </Point>\n'
 | ||
|         kmlStr += '</Placemark>\n'
 | ||
| 
 | ||
|     kmlStr += '</Document>\n'
 | ||
|     kmlStr += '</kml>'
 | ||
|     with open('unittest_decoy.kml', 'w+') as kmlFile:
 | ||
|         kmlFile.write(kmlStr)
 | ||
| 
 | ||
| 
 | ||
| def _testSkills() -> None:
 | ||
|     print('testSkills')
 | ||
|     actor_json = {
 | ||
|         'hasOccupation': [
 | ||
|             {
 | ||
|                 '@type': 'Occupation',
 | ||
|                 'name': "Sysop",
 | ||
|                 "occupationLocation": {
 | ||
|                     "@type": "City",
 | ||
|                     "name": "Fediverse"
 | ||
|                 },
 | ||
|                 'skills': []
 | ||
|             }
 | ||
|         ]
 | ||
|     }
 | ||
|     skillsDict = {
 | ||
|         'bakery': 40,
 | ||
|         'gardening': 70
 | ||
|     }
 | ||
|     setSkillsFromDict(actor_json, skillsDict)
 | ||
|     assert actorHasSkill(actor_json, 'bakery')
 | ||
|     assert actorHasSkill(actor_json, 'gardening')
 | ||
|     assert actorSkillValue(actor_json, 'bakery') == 40
 | ||
|     assert actorSkillValue(actor_json, 'gardening') == 70
 | ||
| 
 | ||
| 
 | ||
| def _testRoles() -> None:
 | ||
|     print('testRoles')
 | ||
|     actor_json = {
 | ||
|         'hasOccupation': [
 | ||
|             {
 | ||
|                 '@type': 'Occupation',
 | ||
|                 'name': "Sysop",
 | ||
|                 'occupationLocation': {
 | ||
|                     '@type': 'City',
 | ||
|                     'name': 'Fediverse'
 | ||
|                 },
 | ||
|                 'skills': []
 | ||
|             }
 | ||
|         ]
 | ||
|     }
 | ||
|     testRolesList = ["admin", "moderator"]
 | ||
|     setRolesFromList(actor_json, testRolesList)
 | ||
|     assert actorHasRole(actor_json, "admin")
 | ||
|     assert actorHasRole(actor_json, "moderator")
 | ||
|     assert not actorHasRole(actor_json, "editor")
 | ||
|     assert not actorHasRole(actor_json, "counselor")
 | ||
|     assert not actorHasRole(actor_json, "artist")
 | ||
| 
 | ||
| 
 | ||
| def _testUserAgentDomain() -> None:
 | ||
|     print('testUserAgentDomain')
 | ||
|     userAgent = \
 | ||
|         'http.rb/4.4.1 (Mastodon/9.10.11; +https://mastodon.something/)'
 | ||
|     assert user_agent_domain(userAgent, False) == 'mastodon.something'
 | ||
|     userAgent = \
 | ||
|         'Mozilla/70.0 (X11; Linux x86_64; rv:1.0) Gecko/20450101 Firefox/1.0'
 | ||
|     assert user_agent_domain(userAgent, False) is None
 | ||
| 
 | ||
| 
 | ||
| def _testSwitchWords(base_dir: str) -> None:
 | ||
|     print('testSwitchWords')
 | ||
|     rules = [
 | ||
|         "rock -> hamster",
 | ||
|         "orange -> lemon"
 | ||
|     ]
 | ||
|     nickname = 'testuser'
 | ||
|     domain = 'testdomain.com'
 | ||
| 
 | ||
|     content = 'This is a test'
 | ||
|     result = switchWords(base_dir, nickname, domain, content, rules)
 | ||
|     assert result == content
 | ||
| 
 | ||
|     content = 'This is orange test'
 | ||
|     result = switchWords(base_dir, nickname, domain, content, rules)
 | ||
|     assert result == 'This is lemon test'
 | ||
| 
 | ||
|     content = 'This is a test rock'
 | ||
|     result = switchWords(base_dir, nickname, domain, content, rules)
 | ||
|     assert result == 'This is a test hamster'
 | ||
| 
 | ||
| 
 | ||
| def _testLimitWordLengths() -> None:
 | ||
|     print('testLimitWordLengths')
 | ||
|     maxWordLength = 13
 | ||
|     text = "This is a test"
 | ||
|     result = limitWordLengths(text, maxWordLength)
 | ||
|     assert result == text
 | ||
| 
 | ||
|     text = "This is an exceptionallylongword test"
 | ||
|     result = limitWordLengths(text, maxWordLength)
 | ||
|     assert result == "This is an exceptionally test"
 | ||
| 
 | ||
| 
 | ||
| def _testLimitRepetedWords() -> None:
 | ||
|     print('limitRepeatedWords')
 | ||
|     text = \
 | ||
|         "This is a preamble.\n\n" + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same\n\n" + \
 | ||
|         "Some other text."
 | ||
|     expected = \
 | ||
|         "This is a preamble.\n\n" + \
 | ||
|         "Same Same Same Same Same Same\n\n" + \
 | ||
|         "Some other text."
 | ||
|     result = limitRepeatedWords(text, 6)
 | ||
|     assert result == expected
 | ||
| 
 | ||
|     text = \
 | ||
|         "This is other preamble.\n\n" + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same " + \
 | ||
|         "Same Same Same Same Same Same Same Same Same Same"
 | ||
|     expected = \
 | ||
|         "This is other preamble.\n\n" + \
 | ||
|         "Same Same Same Same Same Same"
 | ||
|     result = limitRepeatedWords(text, 6)
 | ||
|     assert result == expected
 | ||
| 
 | ||
| 
 | ||
| def _testSetActorLanguages():
 | ||
|     print('testSetActorLanguages')
 | ||
|     actor_json = {
 | ||
|         "attachment": []
 | ||
|     }
 | ||
|     setActorLanguages(None, actor_json, 'es, fr, en')
 | ||
|     assert len(actor_json['attachment']) == 1
 | ||
|     assert actor_json['attachment'][0]['name'] == 'Languages'
 | ||
|     assert actor_json['attachment'][0]['type'] == 'PropertyValue'
 | ||
|     assert isinstance(actor_json['attachment'][0]['value'], str)
 | ||
|     assert ',' in actor_json['attachment'][0]['value']
 | ||
|     lang_list = get_actor_languages_list(actor_json)
 | ||
|     assert 'en' in lang_list
 | ||
|     assert 'fr' in lang_list
 | ||
|     assert 'es' in lang_list
 | ||
|     languagesStr = getActorLanguages(actor_json)
 | ||
|     assert languagesStr == 'en / es / fr'
 | ||
| 
 | ||
| 
 | ||
| def _testGetLinksFromContent():
 | ||
|     print('testGetLinksFromContent')
 | ||
|     content = 'This text has no links'
 | ||
|     links = getLinksFromContent(content)
 | ||
|     assert not links
 | ||
| 
 | ||
|     link1 = 'https://somewebsite.net'
 | ||
|     link2 = 'http://somewhere.or.other'
 | ||
|     content = \
 | ||
|         'This is <a href="' + link1 + '">@linked</a>. ' + \
 | ||
|         'And <a href="' + link2 + '">another</a>.'
 | ||
|     links = getLinksFromContent(content)
 | ||
|     assert len(links.items()) == 2
 | ||
|     assert links.get('@linked')
 | ||
|     assert links['@linked'] == link1
 | ||
|     assert links.get('another')
 | ||
|     assert links['another'] == link2
 | ||
| 
 | ||
|     contentPlain = '<p>' + remove_html(content) + '</p>'
 | ||
|     assert '>@linked</a>' not in contentPlain
 | ||
|     content = addLinksToContent(contentPlain, links)
 | ||
|     assert '>@linked</a>' in content
 | ||
| 
 | ||
| 
 | ||
| def _testAuthorizeSharedItems():
 | ||
|     print('testAuthorizeSharedItems')
 | ||
|     shared_items_fed_domains = \
 | ||
|         ['dog.domain', 'cat.domain', 'birb.domain']
 | ||
|     tokensJson = \
 | ||
|         generateSharedItemFederationTokens(shared_items_fed_domains, None)
 | ||
|     tokensJson = \
 | ||
|         createSharedItemFederationToken(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 authorizeSharedItems(shared_items_fed_domains, None,
 | ||
|                                     'birb.domain',
 | ||
|                                     'cat.domain', 'M' * 86,
 | ||
|                                     False, tokensJson)
 | ||
|     assert authorizeSharedItems(shared_items_fed_domains, None,
 | ||
|                                 'birb.domain',
 | ||
|                                 'cat.domain', tokensJson['cat.domain'],
 | ||
|                                 False, tokensJson)
 | ||
|     tokensJson = \
 | ||
|         updateSharedItemFederationToken(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 = mergeSharedItemTokens(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 _testDateConversions() -> 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 _testValidPassword():
 | ||
|     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 _testGetPriceFromString() -> None:
 | ||
|     print('testGetPriceFromString')
 | ||
|     price, curr = getPriceFromString("5.23")
 | ||
|     assert price == "5.23"
 | ||
|     assert curr == "EUR"
 | ||
|     price, curr = getPriceFromString("£7.36")
 | ||
|     assert price == "7.36"
 | ||
|     assert curr == "GBP"
 | ||
|     price, curr = getPriceFromString("$10.63")
 | ||
|     assert price == "10.63"
 | ||
|     assert curr == "USD"
 | ||
| 
 | ||
| 
 | ||
| def _translateOntology(base_dir: str) -> None:
 | ||
|     return
 | ||
|     ontologyTypes = get_category_types(base_dir)
 | ||
|     url = 'https://translate.astian.org'
 | ||
|     apiKey = None
 | ||
|     ltLangList = libretranslateLanguages(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('<p>', '')
 | ||
|                         translatedStr = translatedStr.replace('</p>', '')
 | ||
|                     ontologyJson['@graph'][index]["rdfs:label"].append({
 | ||
|                         "@value": translatedStr,
 | ||
|                         "@language": lang
 | ||
|                     })
 | ||
|                     changed = True
 | ||
|         if not changed:
 | ||
|             continue
 | ||
|         save_json(ontologyJson, filename + '.new')
 | ||
| 
 | ||
| 
 | ||
| def _testCanReplyTo(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 = \
 | ||
|         createPublicPost(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 _testSecondsBetweenPublished() -> None:
 | ||
|     print('testSecondsBetweenPublished')
 | ||
|     published1 = "2021-10-14T09:39:27Z"
 | ||
|     published2 = "2021-10-14T09:41:28Z"
 | ||
| 
 | ||
|     secondsElapsed = secondsBetweenPublished(published1, published2)
 | ||
|     assert secondsElapsed == 121
 | ||
|     # invalid date
 | ||
|     published2 = "2021-10-14N09:41:28Z"
 | ||
|     secondsElapsed = secondsBetweenPublished(published1, published2)
 | ||
|     assert secondsElapsed == -1
 | ||
| 
 | ||
| 
 | ||
| def _testWordsSimilarity() -> None:
 | ||
|     print('testWordsSimilarity')
 | ||
|     minWords = 10
 | ||
|     content1 = "This is the same"
 | ||
|     content2 = "This is the same"
 | ||
|     assert wordsSimilarity(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 = wordsSimilarity(content1, content2, minWords)
 | ||
|     assert similarity > 70
 | ||
|     content1 = "<p>We're growing! </p><p>A new denizen " + \
 | ||
|         "is frequenting HackBucket. You probably know him already " + \
 | ||
|         "from her epic typos - but let's not spoil too much " + \
 | ||
|         "\ud83d\udd2e</p>"
 | ||
|     content2 = "<p>We're growing! </p><p>A new denizen " + \
 | ||
|         "is frequenting HackBucket. You probably know them already " + \
 | ||
|         "from their epic typos - but let's not spoil too much " + \
 | ||
|         "\ud83d\udd2e</p>"
 | ||
|     similarity = wordsSimilarity(content1, content2, minWords)
 | ||
|     assert similarity > 80
 | ||
| 
 | ||
| 
 | ||
| def _testAddCWfromLists(base_dir: str) -> None:
 | ||
|     print('testAddCWfromLists')
 | ||
|     translate = {}
 | ||
|     cw_lists = loadCWLists(base_dir, True)
 | ||
|     assert cw_lists
 | ||
| 
 | ||
|     post_json_object = {
 | ||
|         "object": {
 | ||
|             "sensitive": False,
 | ||
|             "summary": None,
 | ||
|             "content": ""
 | ||
|         }
 | ||
|     }
 | ||
|     addCWfromLists(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"
 | ||
|         }
 | ||
|     }
 | ||
|     addCWfromLists(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"
 | ||
|         }
 | ||
|     }
 | ||
|     addCWfromLists(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 _testValidEmojiContent() -> None:
 | ||
|     print('testValidEmojiContent')
 | ||
|     assert not validEmojiContent(None)
 | ||
|     assert not validEmojiContent(' ')
 | ||
|     assert not validEmojiContent('j')
 | ||
|     assert not validEmojiContent('😀😀😀')
 | ||
|     assert validEmojiContent('😀')
 | ||
|     assert validEmojiContent('😄')
 | ||
| 
 | ||
| 
 | ||
| def _testHttpsigBaseNew(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 = \
 | ||
|         createPerson(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 = \
 | ||
|             signPostHeadersNew(dateStr, privateKeyPem, nickname,
 | ||
|                                domain, port,
 | ||
|                                hostDomain, port,
 | ||
|                                boxpath, http_prefix, messageBodyJsonStr,
 | ||
|                                algorithm, digestAlgorithm, debug)
 | ||
|     else:
 | ||
|         digestPrefix = getDigestPrefix(digestAlgorithm)
 | ||
|         bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm)
 | ||
|         contentLength = len(messageBodyJsonStr)
 | ||
|         headers = {
 | ||
|             'host': headersDomain,
 | ||
|             'date': dateStr,
 | ||
|             'digest': f'{digestPrefix}={bodyDigest}',
 | ||
|             'content-type': content_type,
 | ||
|             'content-length': str(contentLength)
 | ||
|         }
 | ||
|         assert getDigestAlgorithmFromHeaders(headers) == digestAlgorithm
 | ||
|         signatureIndexHeader, signatureHeader = \
 | ||
|             signPostHeadersNew(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 verifyPostHeaders(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 verifyPostHeaders(http_prefix, publicKeyPem, headers,
 | ||
|                                  boxpath, GETmethod, None,
 | ||
|                                  messageBodyJsonStr, debug) is False
 | ||
|     assert verifyPostHeaders(http_prefix, publicKeyPem, headers,
 | ||
|                              '/parambulator' + boxpath, GETmethod, None,
 | ||
|                              messageBodyJsonStr, debug) is False
 | ||
|     assert verifyPostHeaders(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 = getDigestPrefix(digestAlgorithm)
 | ||
|         bodyDigest = messageContentDigest(messageBodyJsonStr, digestAlgorithm)
 | ||
|         headers = {
 | ||
|             'host': domain,
 | ||
|             'date': dateStr,
 | ||
|             'digest': f'{digestPrefix}={bodyDigest}',
 | ||
|             'content-type': content_type,
 | ||
|             'content-length': str(contentLength)
 | ||
|         }
 | ||
|         assert getDigestAlgorithmFromHeaders(headers) == digestAlgorithm
 | ||
|     headers['signature'] = signatureHeader
 | ||
|     headers['signature-input'] = signatureIndexHeader
 | ||
|     pprint(headers)
 | ||
|     assert verifyPostHeaders(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 _testGetActorFromInReplyTo() -> None:
 | ||
|     print('testGetActorFromInReplyTo')
 | ||
|     inReplyTo = \
 | ||
|         'https://fosstodon.org/users/bashrc/statuses/107400700612621140'
 | ||
|     replyActor = getActorFromInReplyTo(inReplyTo)
 | ||
|     assert replyActor == 'https://fosstodon.org/users/bashrc'
 | ||
| 
 | ||
|     inReplyTo = 'https://fosstodon.org/activity/107400700612621140'
 | ||
|     replyActor = getActorFromInReplyTo(inReplyTo)
 | ||
|     assert replyActor is None
 | ||
| 
 | ||
| 
 | ||
| def runAllTests():
 | ||
|     base_dir = os.getcwd()
 | ||
|     print('Running tests...')
 | ||
|     updateDefaultThemesList(os.getcwd())
 | ||
|     _translateOntology(base_dir)
 | ||
|     _testGetPriceFromString()
 | ||
|     _testFunctions()
 | ||
|     _testGetActorFromInReplyTo()
 | ||
|     _testValidEmojiContent()
 | ||
|     _testAddCWfromLists(base_dir)
 | ||
|     _testWordsSimilarity()
 | ||
|     _testSecondsBetweenPublished()
 | ||
|     _testSignAndVerify()
 | ||
|     _testDangerousSVG(base_dir)
 | ||
|     _testCanReplyTo(base_dir)
 | ||
|     _testDateConversions()
 | ||
|     _testAuthorizeSharedItems()
 | ||
|     _testValidPassword()
 | ||
|     _testGetLinksFromContent()
 | ||
|     _testSetActorLanguages()
 | ||
|     _testLimitRepetedWords()
 | ||
|     _testLimitWordLengths()
 | ||
|     _testSwitchWords(base_dir)
 | ||
|     _testUserAgentDomain()
 | ||
|     _testRoles()
 | ||
|     _testSkills()
 | ||
|     _testSpoofGeolocation()
 | ||
|     _testRemovePostInteractions()
 | ||
|     _testExtractPGPPublicKey()
 | ||
|     _testEmojiImages()
 | ||
|     _testCamelCaseSplit()
 | ||
|     _testSpeakerReplaceLinks()
 | ||
|     _testExtractTextFieldsInPOST()
 | ||
|     _testMarkdownToHtml()
 | ||
|     _testValidHashTag()
 | ||
|     _testPrepareHtmlPostNickname()
 | ||
|     _testDomainHandling()
 | ||
|     _testMastoApi()
 | ||
|     _testLinksWithinPost(base_dir)
 | ||
|     _testReplyToPublicPost(base_dir)
 | ||
|     _testGetMentionedPeople(base_dir)
 | ||
|     _testGuessHashtagCategory()
 | ||
|     _testValidNickname()
 | ||
|     _testParseFeedDate()
 | ||
|     _testFirstParagraphFromString()
 | ||
|     _testGetNewswireTags()
 | ||
|     _testHashtagRuleTree()
 | ||
|     _testRemoveHtmlTag()
 | ||
|     _testReplaceEmailQuote()
 | ||
|     _testConstantTimeStringCheck()
 | ||
|     _testTranslations(base_dir)
 | ||
|     _testValidContentWarning()
 | ||
|     _testRemoveIdEnding()
 | ||
|     _testJsonPostAllowsComments()
 | ||
|     _runHtmlReplaceQuoteMarks()
 | ||
|     _testDangerousCSS(base_dir)
 | ||
|     _testDangerousMarkup()
 | ||
|     _testRemoveHtml()
 | ||
|     _testSiteIsActive()
 | ||
|     _testJsonld()
 | ||
|     _testRemoveTextFormatting()
 | ||
|     _testWebLinks()
 | ||
|     _testRecentPostsCache()
 | ||
|     _testTheme()
 | ||
|     _testSaveLoadJson()
 | ||
|     _testJsonString()
 | ||
|     _testGetStatusNumber()
 | ||
|     _testAddEmoji(base_dir)
 | ||
|     _testActorParsing()
 | ||
|     _testHttpsig(base_dir)
 | ||
|     _testHttpSignedGET(base_dir)
 | ||
|     _testHttpSigNew('rsa-sha256', 'rsa-sha256')
 | ||
|     _testHttpsigBaseNew(True, base_dir, 'rsa-sha256', 'rsa-sha256')
 | ||
|     _testHttpsigBaseNew(False, base_dir, 'rsa-sha256', 'rsa-sha256')
 | ||
|     _testCache()
 | ||
|     _testThreads()
 | ||
|     _testCreatePerson(base_dir)
 | ||
|     _testAuthentication(base_dir)
 | ||
|     _testFollowersOfPerson(base_dir)
 | ||
|     _testNoOfFollowersOnDomain(base_dir)
 | ||
|     _testFollows(base_dir)
 | ||
|     _testGroupFollowers(base_dir)
 | ||
|     print('Tests succeeded\n')
 |