rename main code directory

This commit is contained in:
Mouse Reeve
2020-09-17 13:30:54 -07:00
parent b42faad556
commit f77c156733
199 changed files with 0 additions and 0 deletions

View File

@ -0,0 +1 @@
from . import *

View File

@ -0,0 +1 @@
from . import *

View File

@ -0,0 +1,27 @@
import datetime
from django.test import TestCase
from fedireads import models
class Author(TestCase):
def setUp(self):
self.book = models.Edition.objects.create(
title='Example Edition',
remote_id='https://example.com/book/1',
)
self.author = models.Author.objects.create(
name='Author fullname',
first_name='Auth',
last_name='Or',
aliases=['One', 'Two'],
bio='bio bio bio',
)
def test_serialize_model(self):
activity = self.author.to_activity()
self.assertEqual(activity['id'], self.author.remote_id)
self.assertIsInstance(activity['aliases'], list)
self.assertEqual(activity['aliases'], ['One', 'Two'])
self.assertEqual(activity['name'], 'Author fullname')

View File

@ -0,0 +1,32 @@
import json
import pathlib
from django.test import TestCase
from fedireads import activitypub, models
class Person(TestCase):
def setUp(self):
self.user = models.User.objects.create_user(
'rat', 'rat@rat.com', 'ratword',
)
datafile = pathlib.Path(__file__).parent.joinpath(
'../data/ap_user.json'
)
self.user_data = json.loads(datafile.read_bytes())
def test_load_user_data(self):
activity = activitypub.Person(**self.user_data)
self.assertEqual(activity.id, 'https://example.com/user/mouse')
self.assertEqual(activity.preferredUsername, 'mouse')
self.assertEqual(activity.type, 'Person')
def test_serialize_model(self):
activity = self.user.to_activity()
self.assertEqual(activity['id'], self.user.remote_id)
self.assertEqual(
activity['endpoints'],
{'sharedInbox': self.user.shared_inbox}
)

View File

@ -0,0 +1,46 @@
import json
import pathlib
from django.test import TestCase
from fedireads import activitypub, models
class Quotation(TestCase):
''' we have hecka ways to create statuses '''
def setUp(self):
self.user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword',
local=False,
inbox='https://example.com/user/mouse/inbox',
outbox='https://example.com/user/mouse/outbox',
remote_id='https://example.com/user/mouse',
)
self.book = models.Edition.objects.create(
title='Example Edition',
remote_id='https://example.com/book/1',
)
datafile = pathlib.Path(__file__).parent.joinpath(
'../data/ap_quotation.json'
)
self.status_data = json.loads(datafile.read_bytes())
def test_quotation_activity(self):
quotation = activitypub.Quotation(**self.status_data)
self.assertEqual(quotation.type, 'Quotation')
self.assertEqual(
quotation.id, 'https://example.com/user/mouse/quotation/13')
self.assertEqual(quotation.content, 'commentary')
self.assertEqual(quotation.quote, 'quote body')
self.assertEqual(quotation.inReplyToBook, 'https://example.com/book/1')
self.assertEqual(
quotation.published, '2020-05-10T02:38:31.150343+00:00')
def test_activity_to_model(self):
activity = activitypub.Quotation(**self.status_data)
quotation = activity.to_model(models.Quotation)
self.assertEqual(quotation.book, self.book)
self.assertEqual(quotation.user, self.user)

View File

@ -0,0 +1 @@
from . import *

View File

@ -0,0 +1,122 @@
''' testing book data connectors '''
from django.test import TestCase
from fedireads import models
from fedireads.connectors.abstract_connector import Mapping,\
update_from_mappings
from fedireads.connectors.fedireads_connector import Connector
class FedireadsConnector(TestCase):
def setUp(self):
self.book = models.Edition.objects.create(title='Example Edition')
models.Connector.objects.create(
identifier='example.com',
connector_file='fedireads_connector',
base_url='https://example.com',
books_url='https:/example.com',
covers_url='https://example.com',
search_url='https://example.com/search?q=',
)
self.connector = Connector('example.com')
self.data = {
'title': 'Unused title',
'ASIN': 'A00BLAH',
'isbn_10': '1234567890',
'isbn_13': 'blahhh',
'blah': 'bip',
'format': 'hardcover',
'series': ['one', 'two'],
}
self.connector.key_mappings = [
Mapping('isbn_10', model=models.Edition),
Mapping('isbn_13'),
Mapping('lccn', model=models.Work),
Mapping('asin', remote_field='ASIN'),
]
def test_create_mapping(self):
mapping = Mapping('isbn')
self.assertEqual(mapping.local_field, 'isbn')
self.assertEqual(mapping.remote_field, 'isbn')
self.assertEqual(mapping.model, None)
self.assertEqual(mapping.formatter('bb'), 'bb')
def test_create_mapping_with_remote(self):
mapping = Mapping('isbn', remote_field='isbn13')
self.assertEqual(mapping.local_field, 'isbn')
self.assertEqual(mapping.remote_field, 'isbn13')
self.assertEqual(mapping.model, None)
self.assertEqual(mapping.formatter('bb'), 'bb')
def test_create_mapping_with_formatter(self):
formatter = lambda x: 'aa' + x
mapping = Mapping('isbn', formatter=formatter)
self.assertEqual(mapping.local_field, 'isbn')
self.assertEqual(mapping.remote_field, 'isbn')
self.assertEqual(mapping.formatter, formatter)
self.assertEqual(mapping.model, None)
self.assertEqual(mapping.formatter('bb'), 'aabb')
def test_update_from_mappings(self):
data = {
'title': 'Unused title',
'isbn_10': '1234567890',
'isbn_13': 'blahhh',
'blah': 'bip',
'format': 'hardcover',
'series': ['one', 'two'],
}
mappings = [
Mapping('isbn_10'),
Mapping('blah'),# not present on self.book
Mapping('physical_format', remote_field='format'),
Mapping('series', formatter=lambda x: x[0]),
]
book = update_from_mappings(self.book, data, mappings)
self.assertEqual(book.title, 'Example Edition')
self.assertEqual(book.isbn_10, '1234567890')
self.assertEqual(book.isbn_13, None)
self.assertEqual(book.physical_format, 'hardcover')
self.assertEqual(book.series, 'one')
def test_match_from_mappings(self):
edition = models.Edition.objects.create(
title='Blah',
isbn_13='blahhh',
)
match = self.connector.match_from_mappings(self.data, models.Edition)
self.assertEqual(match, edition)
def test_match_from_mappings_with_model(self):
edition = models.Edition.objects.create(
title='Blah',
isbn_10='1234567890',
)
match = self.connector.match_from_mappings(self.data, models.Edition)
self.assertEqual(match, edition)
def test_match_from_mappings_with_remote(self):
edition = models.Edition.objects.create(
title='Blah',
asin='A00BLAH',
)
match = self.connector.match_from_mappings(self.data, models.Edition)
self.assertEqual(match, edition)
def test_match_from_mappings_no_match(self):
edition = models.Edition.objects.create(
title='Blah',
)
match = self.connector.match_from_mappings(self.data, models.Edition)
self.assertEqual(match, None)

View File

@ -0,0 +1,64 @@
''' testing book data connectors '''
from dateutil import parser
from django.test import TestCase
import json
import pathlib
from fedireads import models
from fedireads.connectors.fedireads_connector import Connector
from fedireads.connectors.abstract_connector import SearchResult, get_date
class FedireadsConnector(TestCase):
def setUp(self):
models.Connector.objects.create(
identifier='example.com',
connector_file='fedireads_connector',
base_url='https://example.com',
books_url='https://example.com',
covers_url='https://example.com/images/covers',
search_url='https://example.com/search?q=',
)
self.connector = Connector('example.com')
work_file = pathlib.Path(__file__).parent.joinpath(
'../data/fr_work.json')
edition_file = pathlib.Path(__file__).parent.joinpath(
'../data/fr_edition.json')
self.work_data = json.loads(work_file.read_bytes())
self.edition_data = json.loads(edition_file.read_bytes())
def test_is_work_data(self):
self.assertEqual(self.connector.is_work_data(self.work_data), True)
self.assertEqual(self.connector.is_work_data(self.edition_data), False)
def test_get_edition_from_work_data(self):
edition = self.connector.get_edition_from_work_data(self.work_data)
self.assertEqual(edition['url'], 'https://example.com/book/122')
def test_get_work_from_edition_data(self):
work = self.connector.get_work_from_edition_date(self.edition_data)
self.assertEqual(work['url'], 'https://example.com/book/121')
def test_format_search_result(self):
datafile = pathlib.Path(__file__).parent.joinpath('../data/fr_search.json')
search_data = json.loads(datafile.read_bytes())
results = self.connector.parse_search_data(search_data)
self.assertIsInstance(results, list)
result = self.connector.format_search_result(results[0])
self.assertIsInstance(result, SearchResult)
self.assertEqual(result.title, 'Jonathan Strange and Mr Norrell')
self.assertEqual(result.key, 'https://example.com/book/122')
self.assertEqual(result.author, 'Susanna Clarke')
self.assertEqual(result.year, 2017)
def test_get_date(self):
date = get_date(self.edition_data['published_date'])
expected = parser.parse("2017-05-10T00:00:00+00:00")
self.assertEqual(date, expected)

View File

@ -0,0 +1,84 @@
''' testing book data connectors '''
from dateutil import parser
from django.test import TestCase
import json
import pathlib
import pytz
from fedireads import models
from fedireads.connectors.openlibrary import Connector
from fedireads.connectors.openlibrary import get_languages, get_description
from fedireads.connectors.openlibrary import pick_default_edition, get_openlibrary_key
from fedireads.connectors.abstract_connector import SearchResult, get_date
class Openlibrary(TestCase):
def setUp(self):
models.Connector.objects.create(
identifier='openlibrary.org',
name='OpenLibrary',
connector_file='openlibrary',
base_url='https://openlibrary.org',
books_url='https://openlibrary.org',
covers_url='https://covers.openlibrary.org',
search_url='https://openlibrary.org/search?q=',
)
self.connector = Connector('openlibrary.org')
work_file = pathlib.Path(__file__).parent.joinpath(
'../data/ol_work.json')
edition_file = pathlib.Path(__file__).parent.joinpath(
'../data/ol_edition.json')
edition_list_file = pathlib.Path(__file__).parent.joinpath(
'../data/ol_edition_list.json')
self.work_data = json.loads(work_file.read_bytes())
self.edition_data = json.loads(edition_file.read_bytes())
self.edition_list_data = json.loads(edition_list_file.read_bytes())
def test_is_work_data(self):
self.assertEqual(self.connector.is_work_data(self.work_data), True)
self.assertEqual(self.connector.is_work_data(self.edition_data), False)
def test_pick_default_edition(self):
edition = pick_default_edition(self.edition_list_data['entries'])
self.assertEqual(edition['key'], '/books/OL9952943M')
def test_format_search_result(self):
''' translate json from openlibrary into SearchResult '''
datafile = pathlib.Path(__file__).parent.joinpath('../data/ol_search.json')
search_data = json.loads(datafile.read_bytes())
results = self.connector.parse_search_data(search_data)
self.assertIsInstance(results, list)
result = self.connector.format_search_result(results[0])
self.assertIsInstance(result, SearchResult)
self.assertEqual(result.title, 'This Is How You Lose the Time War')
self.assertEqual(result.key, 'https://openlibrary.org/works/OL20639540W')
self.assertEqual(result.author, 'Amal El-Mohtar, Max Gladstone')
self.assertEqual(result.year, 2019)
def test_get_description(self):
description = get_description(self.work_data['description'])
expected = 'First in the Old Kingdom/Abhorsen series.'
self.assertEqual(description, expected)
def test_get_date(self):
date = get_date(self.work_data['first_publish_date'])
expected = pytz.utc.localize(parser.parse('1995'))
self.assertEqual(date, expected)
def test_get_languages(self):
languages = get_languages(self.edition_data['languages'])
self.assertEqual(languages, ['English'])
def test_get_ol_key(self):
key = get_openlibrary_key('/books/OL27320736M')
self.assertEqual(key, 'OL27320736M')

View File

@ -0,0 +1,75 @@
''' testing book data connectors '''
import datetime
from django.test import TestCase
from fedireads import models
from fedireads.connectors.self_connector import Connector
from fedireads.settings import DOMAIN
class SelfConnector(TestCase):
def setUp(self):
models.Connector.objects.create(
identifier=DOMAIN,
name='Local',
local=True,
connector_file='self_connector',
base_url='https://%s' % DOMAIN,
books_url='https://%s/book' % DOMAIN,
covers_url='https://%s/images/covers' % DOMAIN,
search_url='https://%s/search?q=' % DOMAIN,
priority=1,
)
self.connector = Connector(DOMAIN)
self.work = models.Work.objects.create(
title='Example Work',
)
self.edition = models.Edition.objects.create(
title='Edition of Example Work',
author_text='Anonymous',
published_date=datetime.datetime(1980, 5, 10),
parent_work=self.work,
)
models.Edition.objects.create(
title='Another Edition',
parent_work=self.work,
series='Anonymous'
)
models.Edition.objects.create(
title='More Editions',
subtitle='The Anonymous Edition',
parent_work=self.work,
)
models.Edition.objects.create(
title='An Edition',
author_text='Fish',
parent_work=self.work
)
def test_format_search_result(self):
result = self.connector.format_search_result(self.edition)
self.assertEqual(result.title, 'Edition of Example Work')
self.assertEqual(result.key, self.edition.remote_id)
self.assertEqual(result.author, 'Anonymous')
self.assertEqual(result.year, 1980)
def test_search_rank(self):
results = self.connector.search('Anonymous')
self.assertEqual(len(results), 3)
self.assertEqual(results[0].title, 'Edition of Example Work')
self.assertEqual(results[1].title, 'More Editions')
self.assertEqual(results[2].title, 'Another Edition')
def test_search_default_filter(self):
self.edition.default = True
self.edition.save()
results = self.connector.search('Anonymous')
self.assertEqual(len(results), 1)
self.assertEqual(results[0].title, 'Edition of Example Work')
results = self.connector.search('Fish')
self.assertEqual(len(results), 1)
self.assertEqual(results[0].title, 'An Edition')

View File

@ -0,0 +1,29 @@
{
"id": "https://example.com/user/mouse/comment/6",
"url": "https://example.com/user/mouse/comment/6",
"inReplyTo": null,
"published": "2020-05-08T23:45:44.768012+00:00",
"attributedTo": "https://example.com/user/mouse",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://example.com/user/mouse/followers"
],
"sensitive": null,
"content": "commentary",
"type": "Comment",
"attachment": [],
"replies": {
"id": "https://example.com/user/mouse/comment/6/replies",
"type": "Collection",
"first": {
"type": "CollectionPage",
"next": "https://example.com/user/mouse/comment/6/replies?only_other_accounts=true&page=true",
"partOf": "https://example.com/user/mouse/comment/6/replies",
"items": []
}
},
"inReplyToBook": "https://example.com/book/1"
}

View File

@ -0,0 +1,36 @@
{
"id": "https://example.com/user/mouse/quotation/13",
"url": "https://example.com/user/mouse/quotation/13",
"inReplyTo": null,
"published": "2020-05-10T02:38:31.150343+00:00",
"attributedTo": "https://example.com/user/mouse",
"to": [
"https://www.w3.org/ns/activitystreams#Public"
],
"cc": [
"https://example.com/user/mouse/followers"
],
"sensitive": false,
"content": "commentary",
"type": "Quotation",
"attachment": [
{
"type": "Document",
"mediaType": "image//images/covers/2b4e4712-5a4d-4ac1-9df4-634cc9c7aff3jpg",
"url": "https://example.com/images/covers/2b4e4712-5a4d-4ac1-9df4-634cc9c7aff3jpg",
"name": "Cover of \"This Is How You Lose the Time War\""
}
],
"replies": {
"id": "https://example.com/user/mouse/quotation/13/replies",
"type": "Collection",
"first": {
"type": "CollectionPage",
"next": "https://example.com/user/mouse/quotation/13/replies?only_other_accounts=true&page=true",
"partOf": "https://example.com/user/mouse/quotation/13/replies",
"items": []
}
},
"inReplyToBook": "https://example.com/book/1",
"quote": "quote body"
}

View File

@ -0,0 +1,36 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value"
}
],
"id": "https://example.com/user/mouse",
"type": "Person",
"preferredUsername": "mouse",
"name": "MOUSE?? MOUSE!!",
"inbox": "https://example.com/user/mouse/inbox",
"outbox": "https://example.com/user/mouse/outbox",
"followers": "https://example.com/user/mouse/followers",
"following": "https://example.com/user/mouse/following",
"summary": "",
"publicKey": {
"id": "https://example.com/user/mouse/#main-key",
"owner": "https://example.com/user/mouse",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6QisDrjOQvkRo/MqNmSYPwqtt\nCxg/8rCW+9jKbFUKvqjTeKVotEE85122v/DCvobCCdfQuYIFdVMk+dB1xJ0iPGPg\nyU79QHY22NdV9mFKA2qtXVVxb5cxpA4PlwOHM6PM/k8B+H09OUrop2aPUAYwy+vg\n+MXyz8bAXrIS1kq6fQIDAQAB\n-----END PUBLIC KEY-----"
},
"endpoints": {
"sharedInbox": "https://example.com/inbox"
},
"fedireadsUser": true,
"manuallyApprovesFollowers": false,
"icon": {
"type": "Image",
"mediaType": "image/png",
"url": "https://example.com/images/avatars/AL-2-crop-50.png"
}
}

View File

@ -0,0 +1,42 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Document",
"book_type": "Edition",
"name": "Jonathan Strange and Mr Norrell",
"url": "https://example.com/book/122",
"authors": [
"https://example.com/author/25"
],
"published_date": "2017-05-10T00:00:00+00:00",
"work": {
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Document",
"book_type": "Work",
"name": "Jonathan Strange and Mr Norrell",
"url": "https://example.com/book/121",
"authors": [
"https://example.com/author/25"
],
"title": "Jonathan Strange and Mr Norrell",
"attachment": [
{
"type": "Document",
"mediaType": "image/jpg",
"url": "https://example.com/images/covers/8775540-M.jpg",
"name": "Cover of \"Jonathan Strange and Mr Norrell\""
}
]
},
"title": "Jonathan Strange and Mr Norrell",
"subtitle": "Bloomsbury Modern Classics",
"isbn_13": "9781408891469",
"physical_format": "paperback",
"attachment": [
{
"type": "Document",
"mediaType": "image/jpg",
"url": "https://example.com/images/covers/9155821-M.jpg",
"name": "Cover of \"Jonathan Strange and Mr Norrell\""
}
]
}

View File

@ -0,0 +1 @@
[{"title": "Jonathan Strange and Mr Norrell", "key": "https://example.com/book/122", "author": "Susanna Clarke", "year": 2017}]

View File

@ -0,0 +1,44 @@
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Document",
"book_type": "Work",
"name": "Jonathan Strange and Mr Norrell",
"url": "https://example.com/book/121",
"authors": [
"https://example.com/author/25"
],
"editions": [
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Document",
"book_type": "Edition",
"name": "Jonathan Strange and Mr Norrell",
"url": "https://example.com/book/122",
"authors": [
"https://example.com/author/25"
],
"published_date": "2017-05-10T00:00:00+00:00",
"title": "Jonathan Strange and Mr Norrell",
"subtitle": "Bloomsbury Modern Classics",
"isbn_13": "9781408891469",
"physical_format": "paperback",
"attachment": [
{
"type": "Document",
"mediaType": "image/jpg",
"url": "https://example.com/images/covers/9155821-M.jpg",
"name": "Cover of \"Jonathan Strange and Mr Norrell\""
}
]
}
],
"title": "Jonathan Strange and Mr Norrell",
"attachment": [
{
"type": "Document",
"mediaType": "image/jpg",
"url": "https://example.com/images/covers/8775540-M.jpg",
"name": "Cover of \"Jonathan Strange and Mr Norrell\""
}
]
}

View File

@ -0,0 +1,83 @@
{
"identifiers": {
"librarything": [
"10014"
],
"goodreads": [
"535197",
"1102517",
"518848"
]
},
"lc_classifications": [
"PZ7.N647 Sab 1995"
],
"latest_revision": 7,
"ocaid": "sabriel00nixg",
"ia_box_id": [
"IA107202"
],
"edition_name": "1st American ed.",
"title": "Sabriel",
"languages": [
{
"key": "/languages/eng"
}
],
"subjects": [
"Fantasy."
],
"publish_country": "nyu",
"by_statement": "Garth Nix.",
"type": {
"key": "/type/edition"
},
"revision": 7,
"publishers": [
"Harper Trophy"
],
"description": {
"type": "/type/text",
"value": "Sabriel, daughter of the necromancer Abhorsen, must journey into the mysterious and magical Old Kingdom to rescue her father from the Land of the Dead."
},
"last_modified": {
"type": "/type/datetime",
"value": "2017-10-08T21:20:07.665236"
},
"key": "/books/OL22951843M",
"authors": [
{
"key": "/authors/OL382982A"
}
],
"publish_places": [
"New York"
],
"pagination": "491 p. :",
"created": {
"type": "/type/datetime",
"value": "2009-02-12T16:29:58.929717"
},
"dewey_decimal_class": [
"[Fic]"
],
"notes": {
"type": "/type/text",
"value": "Originally published: Australia : HarperCollins, 1995."
},
"number_of_pages": 491,
"lccn": [
"96001295"
],
"isbn_10": [
"0060273224",
"0060273232",
"0064471837"
],
"publish_date": "1996",
"works": [
{
"key": "/works/OL15832982W"
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,132 @@
{
"start": 0,
"num_found": 2,
"numFound": 2,
"docs": [
{
"title_suggest": "This Is How You Lose the Time War",
"edition_key": [
"OL27901088M"
],
"isbn": [
"9781534431003",
"1534431004"
],
"has_fulltext": false,
"text": [
"OL27901088M",
"9781534431003",
"1534431004",
"Amal El-Mohtar",
"Max Gladstone",
"OL7313207A",
"OL7129451A",
"epistolary",
"science fiction",
"time-traveling",
"LGBT",
"This Is How You Lose the Time War",
"/works/OL20639540W",
"Simon and Schuster",
"Atlantis",
"London",
"The whole of time and space"
],
"author_name": [
"Amal El-Mohtar",
"Max Gladstone"
],
"seed": [
"/books/OL27901088M",
"/works/OL20639540W",
"/subjects/science_fiction",
"/subjects/time-traveling",
"/subjects/epistolary",
"/subjects/lgbt",
"/subjects/place:london",
"/subjects/place:atlantis",
"/subjects/time:the_whole_of_time_and_space",
"/authors/OL7313207A",
"/authors/OL7129451A"
],
"author_key": [
"OL7313207A",
"OL7129451A"
],
"availability": {
"status": "error"
},
"subject": [
"epistolary",
"science fiction",
"time-traveling",
"LGBT"
],
"title": "This Is How You Lose the Time War",
"publish_date": [
"July 16, 2019"
],
"type": "work",
"ebook_count_i": 0,
"publish_place": [
"New York, USA"
],
"edition_count": 1,
"key": "/works/OL20639540W",
"publisher": [
"Simon and Schuster"
],
"language": [
"eng"
],
"last_modified_i": 1579909341,
"cover_edition_key": "OL27901088M",
"publish_year": [
2019
],
"first_publish_year": 2019,
"place": [
"Atlantis",
"London"
],
"time": [
"The whole of time and space"
]
},
{
"title_suggest": "This is How You Lose the Time War",
"cover_i": 8665647,
"has_fulltext": false,
"title": "This is How You Lose the Time War",
"last_modified_i": 1561998020,
"edition_count": 0,
"author_name": [
"Amal El-Mohtar",
"Max Gladstone"
],
"seed": [
"/works/OL19859295W",
"/authors/OL7313207A",
"/authors/OL7129451A"
],
"key": "/works/OL19859295W",
"text": [
"Amal El-Mohtar",
"Max Gladstone",
"OL7313207A",
"OL7129451A",
"This is How You Lose the Time War",
"/works/OL19859295W"
],
"author_key": [
"OL7313207A",
"OL7129451A"
],
"type": "work",
"availability": {
"status": "error"
},
"ebook_count_i": 0
}
]
}

View File

@ -0,0 +1,63 @@
{
"first_publish_date": "1995",
"key": "/works/OL15832982W",
"description": {
"type": "/type/text",
"value": "First in the Old Kingdom/Abhorsen series."
},
"created": {
"type": "/type/datetime",
"value": "2011-07-07T16:30:28.384311"
},
"title": "Sabriel",
"covers": [
6796986,
3843137
],
"first_sentence": {
"type": "/type/text",
"value": "THE RABBIT HAD been run over minutes before."
},
"excerpts": [
{
"excerpt": "THE RABBIT HAD been run over minutes before."
}
],
"lc_classifications": [
"PZ7.N647 Sab 1995"
],
"latest_revision": 5,
"last_modified": {
"type": "/type/datetime",
"value": "2019-07-22T13:57:34.579651"
},
"authors": [
{
"type": {
"key": "/type/author_role"
},
"author": {
"key": "/authors/OL382982A"
}
}
],
"dewey_number": [
"[Fic]"
],
"subjects": [
"Fantasy",
"Science Fiction & Fantasy",
"Fantasy fiction",
"Fiction",
"Juvenile Fiction",
"Juvenile fiction",
"Magical thinking"
],
"type": {
"key": "/type/work"
},
"subject_times": [
"Life and Death."
],
"revision": 5
}

View File

@ -0,0 +1 @@
from . import *

View File

@ -0,0 +1,25 @@
''' testing models '''
from django.test import TestCase
from fedireads import models
from fedireads.models.base_model import FedireadsModel
from fedireads.settings import DOMAIN
class BaseModel(TestCase):
def test_remote_id(self):
instance = FedireadsModel()
instance.id = 1
expected = instance.get_remote_id()
self.assertEqual(expected, 'https://%s/fedireadsmodel/1' % DOMAIN)
def test_remote_id_with_user(self):
user = models.User.objects.create_user(
'mouse', 'mouse@mouse.com', 'mouseword')
instance = FedireadsModel()
instance.user = user
instance.id = 1
expected = instance.get_remote_id()
self.assertEqual(
expected,
'https://%s/user/mouse/fedireadsmodel/1' % DOMAIN)

View File

@ -0,0 +1,63 @@
''' testing models '''
from django.test import TestCase
from fedireads import models, settings
class Book(TestCase):
''' not too much going on in the books model but here we are '''
def setUp(self):
self.work = models.Work.objects.create(
title='Example Work',
remote_id='https://example.com/book/1'
)
self.first_edition = models.Edition.objects.create(
title='Example Edition',
parent_work=self.work,
)
self.second_edition = models.Edition.objects.create(
title='Another Example Edition',
parent_work=self.work,
)
def test_remote_id(self):
local_id = 'https://%s/book/%d' % (settings.DOMAIN, self.work.id)
self.assertEqual(self.work.get_remote_id(), local_id)
self.assertEqual(self.work.remote_id, 'https://example.com/book/1')
def test_local_id(self):
''' the local_id property for books '''
expected_id = 'https://%s/book/%d' % (settings.DOMAIN, self.work.id)
self.assertEqual(self.work.local_id, expected_id)
def test_create_book(self):
''' you shouldn't be able to create Books (only editions and works) '''
self.assertRaises(
ValueError,
models.Book.objects.create,
title='Invalid Book'
)
def test_default_edition(self):
''' a work should always be able to produce a deafult edition '''
self.assertIsInstance(self.work.default_edition, models.Edition)
self.assertEqual(self.work.default_edition, self.first_edition)
self.second_edition.default = True
self.second_edition.save()
self.assertEqual(self.work.default_edition, self.second_edition)
class Shelf(TestCase):
def setUp(self):
user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword')
models.Shelf.objects.create(
name='Test Shelf', identifier='test-shelf', user=user)
def test_remote_id(self):
''' editions and works use the same absolute id syntax '''
shelf = models.Shelf.objects.get(identifier='test-shelf')
expected_id = 'https://%s/user/mouse/shelf/test-shelf' % settings.DOMAIN
self.assertEqual(shelf.get_remote_id(), expected_id)

View File

@ -0,0 +1,112 @@
''' testing models '''
import datetime
from django.test import TestCase
from fedireads import models
class ImportJob(TestCase):
''' this is a fancy one!!! '''
def setUp(self):
''' data is from a goodreads export of The Raven Tower '''
read_data = {
'Book Id': 39395857,
'Title': 'The Raven Tower',
'Author': 'Ann Leckie',
'Author l-f': 'Leckie, Ann',
'Additional Authors': '',
'ISBN': '="0356506991"',
'ISBN13': '="9780356506999"',
'My Rating': 0,
'Average Rating': 4.06,
'Publisher': 'Orbit',
'Binding': 'Hardcover',
'Number of Pages': 416,
'Year Published': 2019,
'Original Publication Year': 2019,
'Date Read': '2019/04/09',
'Date Added': '2019/04/09',
'Bookshelves': '',
'Bookshelves with positions': '',
'Exclusive Shelf': 'read',
'My Review': '',
'Spoiler': '',
'Private Notes': '',
'Read Count': 1,
'Recommended For': '',
'Recommended By': '',
'Owned Copies': 0,
'Original Purchase Date': '',
'Original Purchase Location': '',
'Condition': '',
'Condition Description': '',
'BCID': ''
}
currently_reading_data = read_data.copy()
currently_reading_data['Exclusive Shelf'] = 'currently-reading'
currently_reading_data['Date Read'] = ''
unknown_read_data = currently_reading_data.copy()
unknown_read_data['Exclusive Shelf'] = 'read'
unknown_read_data['Date Read'] = ''
user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword')
job = models.ImportJob.objects.create(user=user)
models.ImportItem.objects.create(
job=job, index=1, data=currently_reading_data)
models.ImportItem.objects.create(
job=job, index=2, data=read_data)
models.ImportItem.objects.create(
job=job, index=3, data=unknown_read_data)
def test_isbn(self):
''' it unquotes the isbn13 field from data '''
expected = '9780356506999'
item = models.ImportItem.objects.get(index=1)
self.assertEqual(item.isbn, expected)
def test_shelf(self):
''' converts to the local shelf typology '''
expected = 'reading'
item = models.ImportItem.objects.get(index=1)
self.assertEqual(item.shelf, expected)
def test_date_added(self):
''' converts to the local shelf typology '''
expected = datetime.datetime(2019, 4, 9, 0, 0)
item = models.ImportItem.objects.get(index=1)
self.assertEqual(item.date_added, expected)
def test_date_read(self):
''' converts to the local shelf typology '''
expected = datetime.datetime(2019, 4, 9, 0, 0)
item = models.ImportItem.objects.get(index=2)
self.assertEqual(item.date_read, expected)
def test_currently_reading_reads(self):
expected = [models.ReadThrough(
start_date=datetime.datetime(2019, 4, 9, 0, 0))]
actual = models.ImportItem.objects.get(index=1)
self.assertEqual(actual.reads[0].start_date, expected[0].start_date)
self.assertEqual(actual.reads[0].finish_date, expected[0].finish_date)
def test_read_reads(self):
expected = [models.ReadThrough(
finish_date=datetime.datetime(2019, 4, 9, 0, 0))]
actual = models.ImportItem.objects.get(index=2)
self.assertEqual(actual.reads[0].start_date, expected[0].start_date)
self.assertEqual(actual.reads[0].finish_date, expected[0].finish_date)
def test_unread_reads(self):
expected = []
actual = models.ImportItem.objects.get(index=3)
self.assertEqual(actual.reads, expected)

View File

@ -0,0 +1,52 @@
''' testing models '''
from django.test import TestCase
from fedireads import models, settings
class Status(TestCase):
def setUp(self):
user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword')
book = models.Edition.objects.create(title='Example Edition')
models.Status.objects.create(user=user, content='Blah blah')
models.Comment.objects.create(user=user, content='content', book=book)
models.Quotation.objects.create(
user=user, content='content', book=book, quote='blah')
models.Review.objects.create(
user=user, content='content', book=book, rating=3)
def test_status(self):
status = models.Status.objects.first()
expected_id = 'https://%s/user/mouse/status/%d' % \
(settings.DOMAIN, status.id)
self.assertEqual(status.remote_id, expected_id)
def test_comment(self):
comment = models.Comment.objects.first()
expected_id = 'https://%s/user/mouse/comment/%d' % \
(settings.DOMAIN, comment.id)
self.assertEqual(comment.remote_id, expected_id)
def test_quotation(self):
quotation = models.Quotation.objects.first()
expected_id = 'https://%s/user/mouse/quotation/%d' % \
(settings.DOMAIN, quotation.id)
self.assertEqual(quotation.remote_id, expected_id)
def test_review(self):
review = models.Review.objects.first()
expected_id = 'https://%s/user/mouse/review/%d' % \
(settings.DOMAIN, review.id)
self.assertEqual(review.remote_id, expected_id)
class Tag(TestCase):
def test_tag(self):
book = models.Edition.objects.create(title='Example Edition')
user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword')
tag = models.Tag.objects.create(user=user, book=book, name='t/est tag')
self.assertEqual(tag.identifier, 't%2Fest+tag')

View File

@ -0,0 +1,34 @@
''' testing models '''
from django.test import TestCase
from fedireads import models
from fedireads.settings import DOMAIN
class User(TestCase):
def setUp(self):
models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword')
def test_computed_fields(self):
''' username instead of id here '''
user = models.User.objects.get(localname='mouse')
expected_id = 'https://%s/user/mouse' % DOMAIN
self.assertEqual(user.remote_id, expected_id)
self.assertEqual(user.username, 'mouse@%s' % DOMAIN)
self.assertEqual(user.localname, 'mouse')
self.assertEqual(user.shared_inbox, 'https://%s/inbox' % DOMAIN)
self.assertEqual(user.inbox, '%s/inbox' % expected_id)
self.assertEqual(user.outbox, '%s/outbox' % expected_id)
self.assertIsNotNone(user.private_key)
self.assertIsNotNone(user.public_key)
def test_user_shelves(self):
user = models.User.objects.get(localname='mouse')
shelves = models.Shelf.objects.filter(user=user).all()
self.assertEqual(len(shelves), 3)
names = [s.name for s in shelves]
self.assertEqual(names, ['To Read', 'Currently Reading', 'Read'])
ids = [s.identifier for s in shelves]
self.assertEqual(ids, ['to-read', 'reading', 'read'])

View File

@ -0,0 +1 @@
from . import *

View File

@ -0,0 +1,18 @@
from django.test import TestCase
from fedireads import models
from fedireads import status as status_builder
class Comment(TestCase):
''' we have hecka ways to create statuses '''
def setUp(self):
self.user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword')
self.book = models.Edition.objects.create(title='Example Edition')
def test_create_comment(self):
comment = status_builder.create_comment(
self.user, self.book, 'commentary')
self.assertEqual(comment.content, 'commentary')

View File

@ -0,0 +1,26 @@
from django.test import TestCase
import json
import pathlib
from fedireads import activitypub, models
from fedireads import status as status_builder
class Quotation(TestCase):
''' we have hecka ways to create statuses '''
def setUp(self):
self.user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword',
remote_id='https://example.com/user/mouse'
)
self.book = models.Edition.objects.create(
title='Example Edition',
remote_id='https://example.com/book/1',
)
def test_create_quotation(self):
quotation = status_builder.create_quotation(
self.user, self.book, 'commentary', 'a quote')
self.assertEqual(quotation.quote, 'a quote')
self.assertEqual(quotation.content, 'commentary')

View File

@ -0,0 +1,39 @@
from django.test import TestCase
from fedireads import models
from fedireads import status as status_builder
class Review(TestCase):
''' we have hecka ways to create statuses '''
def setUp(self):
self.user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword')
self.book = models.Edition.objects.create(title='Example Edition')
def test_create_review(self):
review = status_builder.create_review(
self.user, self.book, 'review name', 'content', 5)
self.assertEqual(review.name, 'review name')
self.assertEqual(review.content, 'content')
self.assertEqual(review.rating, 5)
review = status_builder.create_review(
self.user, self.book, '<div>review</div> name', '<b>content', 5)
self.assertEqual(review.name, 'review name')
self.assertEqual(review.content, 'content')
self.assertEqual(review.rating, 5)
def test_review_rating(self):
review = status_builder.create_review(
self.user, self.book, 'review name', 'content', -1)
self.assertEqual(review.name, 'review name')
self.assertEqual(review.content, 'content')
self.assertEqual(review.rating, None)
review = status_builder.create_review(
self.user, self.book, 'review name', 'content', 6)
self.assertEqual(review.name, 'review name')
self.assertEqual(review.content, 'content')
self.assertEqual(review.rating, None)

View File

@ -0,0 +1,28 @@
from django.test import TestCase
from fedireads import models
from fedireads import status as status_builder
class Status(TestCase):
''' we have hecka ways to create statuses '''
def setUp(self):
self.user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword',
local=False,
inbox='https://example.com/user/mouse/inbox',
outbox='https://example.com/user/mouse/outbox',
remote_id='https://example.com/user/mouse'
)
def test_create_status(self):
content = 'statuses are usually <i>replies</i>'
status = status_builder.create_status(
self.user, content)
self.assertEqual(status.content, content)
reply = status_builder.create_status(
self.user, content, reply_parent=status)
self.assertEqual(reply.content, content)
self.assertEqual(reply.reply_parent, status)

View File

@ -0,0 +1,37 @@
from django.test import TestCase
from fedireads import books_manager, models
from fedireads.connectors.fedireads_connector import Connector
from fedireads.settings import DOMAIN
class Book(TestCase):
def setUp(self):
self.work = models.Work.objects.create(
title='Example Work'
)
self.edition = models.Edition.objects.create(
title='Example Edition',
parent_work=self.work
)
def test_get_edition(self):
edition = books_manager.get_edition(self.edition.id)
self.assertEqual(edition, self.edition)
def test_get_edition_work(self):
edition = books_manager.get_edition(self.work.id)
self.assertEqual(edition, self.edition)
def test_get_or_create_connector(self):
remote_id = 'https://example.com/object/1'
connector = books_manager.get_or_create_connector(remote_id)
self.assertIsInstance(connector, Connector)
self.assertEqual(connector.identifier, 'example.com')
self.assertEqual(connector.base_url, 'https://example.com')
same_connector = books_manager.get_or_create_connector(remote_id)
self.assertEqual(connector.identifier, same_connector.identifier)

View File

@ -0,0 +1,77 @@
from django.test import TestCase
from fedireads import models, broadcast
from fedireads.settings import DOMAIN
class Book(TestCase):
def setUp(self):
self.user = models.User.objects.create_user(
'mouse', 'mouse@mouse.mouse', 'mouseword')
follower = models.User.objects.create_user(
'rat', 'rat@mouse.mouse', 'ratword', local=False,
remote_id='http://example.com/u/1',
outbox='http://example.com/u/1/o',
shared_inbox='http://example.com/inbox',
inbox='http://example.com/u/1/inbox')
self.user.followers.add(follower)
no_inbox_follower = models.User.objects.create_user(
'hamster', 'hamster@mouse.mouse', 'hamword',
shared_inbox=None, local=False,
remote_id='http://example.com/u/2',
outbox='http://example.com/u/2/o',
inbox='http://example.com/u/2/inbox')
self.user.followers.add(no_inbox_follower)
non_fr_follower = models.User.objects.create_user(
'gerbil', 'gerb@mouse.mouse', 'gerbword',
remote_id='http://example.com/u/3',
outbox='http://example2.com/u/3/o',
inbox='http://example2.com/u/3/inbox',
shared_inbox='http://example2.com/inbox',
fedireads_user=False, local=False)
self.user.followers.add(non_fr_follower)
local_follower = models.User.objects.create_user(
'joe', 'joe@mouse.mouse', 'jeoword')
self.user.followers.add(local_follower)
models.User.objects.create_user(
'nutria', 'nutria@mouse.mouse', 'nuword',
remote_id='http://example.com/u/4',
outbox='http://example.com/u/4/o',
shared_inbox='http://example.com/inbox',
inbox='http://example.com/u/4/inbox',
local=False)
def test_get_public_recipients(self):
expected = [
'http://example2.com/inbox',
'http://example.com/inbox',
'http://example.com/u/2/inbox',
]
recipients = broadcast.get_public_recipients(self.user)
self.assertEqual(recipients, expected)
def test_get_public_recipients_software(self):
expected = [
'http://example.com/inbox',
'http://example.com/u/2/inbox',
]
recipients = broadcast.get_public_recipients(self.user, software='fedireads')
self.assertEqual(recipients, expected)
def test_get_public_recipients_software_other(self):
expected = [
'http://example2.com/inbox',
]
recipients = broadcast.get_public_recipients(self.user, software='mastodon')
self.assertEqual(recipients, expected)

View File

@ -0,0 +1,59 @@
import json
import pathlib
from django.test import TestCase
from fedireads import models, incoming
class Favorite(TestCase):
''' not too much going on in the books model but here we are '''
def setUp(self):
self.remote_user = models.User.objects.create_user(
'rat', 'rat@rat.com', 'ratword',
local=False,
remote_id='https://example.com/users/rat',
inbox='https://example.com/users/rat/inbox',
outbox='https://example.com/users/rat/outbox',
)
self.local_user = models.User.objects.create_user(
'mouse', 'mouse@mouse.com', 'mouseword',
remote_id='http://local.com/user/mouse')
self.status = models.Status.objects.create(
user=self.local_user,
content='Test status',
remote_id='http://local.com/status/1',
)
datafile = pathlib.Path(__file__).parent.joinpath(
'data/ap_user.json'
)
self.user_data = json.loads(datafile.read_bytes())
def test_handle_favorite(self):
activity = {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'http://example.com/activity/1',
'type': 'Create',
'actor': 'https://example.com/users/rat',
'published': 'Mon, 25 May 2020 19:31:20 GMT',
'to': ['https://example.com/user/rat/followers'],
'cc': ['https://www.w3.org/ns/activitystreams#Public'],
'object': {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': 'http://example.com/fav/1',
'type': 'Like',
'actor': 'https://example.com/users/rat',
'object': 'http://local.com/status/1',
},
'signature': {}
}
result = incoming.handle_favorite(activity)
fav = models.Favorite.objects.get(remote_id='http://example.com/fav/1')
self.assertEqual(fav.status, self.status)
self.assertEqual(fav.remote_id, 'http://example.com/fav/1')
self.assertEqual(fav.user, self.remote_user)

View File

@ -0,0 +1,70 @@
import json
import pathlib
from django.test import TestCase
from fedireads import models, remote_user
class RemoteUser(TestCase):
''' not too much going on in the books model but here we are '''
def setUp(self):
self.remote_user = models.User.objects.create_user(
'rat', 'rat@rat.com', 'ratword',
local=False,
remote_id='https://example.com/users/rat',
inbox='https://example.com/users/rat/inbox',
outbox='https://example.com/users/rat/outbox',
)
datafile = pathlib.Path(__file__).parent.joinpath(
'data/ap_user.json'
)
self.user_data = json.loads(datafile.read_bytes())
def test_get_remote_user(self):
actor = 'https://example.com/users/rat'
user = remote_user.get_or_create_remote_user(actor)
self.assertEqual(user, self.remote_user)
def test_create_remote_user(self):
user = remote_user.create_remote_user(self.user_data)
self.assertFalse(user.local)
self.assertEqual(user.remote_id, 'https://example.com/user/mouse')
self.assertEqual(user.username, 'mouse@example.com')
self.assertEqual(user.name, 'MOUSE?? MOUSE!!')
self.assertEqual(user.inbox, 'https://example.com/user/mouse/inbox')
self.assertEqual(user.outbox, 'https://example.com/user/mouse/outbox')
self.assertEqual(user.shared_inbox, 'https://example.com/inbox')
self.assertEqual(
user.public_key,
self.user_data['publicKey']['publicKeyPem']
)
self.assertEqual(user.local, False)
self.assertEqual(user.fedireads_user, True)
self.assertEqual(user.manually_approves_followers, False)
def test_create_remote_user_missing_inbox(self):
del self.user_data['inbox']
self.assertRaises(
TypeError,
remote_user.create_remote_user,
self.user_data
)
def test_create_remote_user_missing_outbox(self):
del self.user_data['outbox']
self.assertRaises(
TypeError,
remote_user.create_remote_user,
self.user_data
)
def test_create_remote_user_default_fields(self):
del self.user_data['manuallyApprovesFollowers']
user = remote_user.create_remote_user(self.user_data)
self.assertEqual(user.manually_approves_followers, False)

View File

@ -0,0 +1,50 @@
from django.test import TestCase
from fedireads.sanitize_html import InputHtmlParser
class Sanitizer(TestCase):
def test_no_html(self):
input_text = 'no html '
parser = InputHtmlParser()
parser.feed(input_text)
output = parser.get_output()
self.assertEqual(input_text, output)
def test_valid_html(self):
input_text = '<b>yes </b> <i>html</i>'
parser = InputHtmlParser()
parser.feed(input_text)
output = parser.get_output()
self.assertEqual(input_text, output)
def test_valid_html_attrs(self):
input_text = '<a href="fish.com">yes </a> <i>html</i>'
parser = InputHtmlParser()
parser.feed(input_text)
output = parser.get_output()
self.assertEqual(input_text, output)
def test_invalid_html(self):
input_text = '<b>yes <i>html</i>'
parser = InputHtmlParser()
parser.feed(input_text)
output = parser.get_output()
self.assertEqual('yes html', output)
input_text = 'yes <i></b>html </i>'
parser = InputHtmlParser()
parser.feed(input_text)
output = parser.get_output()
self.assertEqual('yes html ', output)
def test_disallowed_html(self):
input_text = '<div> yes <i>html</i></div>'
parser = InputHtmlParser()
parser.feed(input_text)
output = parser.get_output()
self.assertEqual(' yes <i>html</i>', output)

View File

@ -0,0 +1,189 @@
import time
from collections import namedtuple
from urllib.parse import urlsplit
import pathlib
import json
import responses
from django.test import TestCase, Client
from django.utils.http import http_date
from fedireads.models import User
from fedireads.activitypub import Follow
from fedireads.settings import DOMAIN
from fedireads.signatures import create_key_pair, make_signature, make_digest
def get_follow_data(follower, followee):
follow_activity = Follow(
id='https://test.com/user/follow/id',
actor=follower.remote_id,
object=followee.remote_id,
).serialize()
return json.dumps(follow_activity)
Sender = namedtuple('Sender', ('remote_id', 'private_key', 'public_key'))
class Signature(TestCase):
def setUp(self):
self.mouse = User.objects.create_user('mouse', 'mouse@example.com', '')
self.rat = User.objects.create_user('rat', 'rat@example.com', '')
self.cat = User.objects.create_user('cat', 'cat@example.com', '')
private_key, public_key = create_key_pair()
self.fake_remote = Sender(
'http://localhost/user/remote',
private_key,
public_key,
)
def send(self, signature, now, data, digest):
c = Client()
return c.post(
urlsplit(self.rat.inbox).path,
data=data,
content_type='application/json',
**{
'HTTP_DATE': now,
'HTTP_SIGNATURE': signature,
'HTTP_DIGEST': digest,
'HTTP_CONTENT_TYPE': 'application/activity+json; charset=utf-8',
'HTTP_HOST': DOMAIN,
}
)
def send_test_request(
self,
sender,
signer=None,
send_data=None,
digest=None,
date=None):
now = date or http_date()
data = get_follow_data(sender, self.rat)
digest = digest or make_digest(data)
signature = make_signature(
signer or sender, self.rat.inbox, now, digest)
return self.send(signature, now, send_data or data, digest)
def test_correct_signature(self):
response = self.send_test_request(sender=self.mouse)
self.assertEqual(response.status_code, 200)
def test_wrong_signature(self):
''' Messages must be signed by the right actor.
(cat cannot sign messages on behalf of mouse)
'''
response = self.send_test_request(sender=self.mouse, signer=self.cat)
self.assertEqual(response.status_code, 401)
@responses.activate
def test_remote_signer(self):
datafile = pathlib.Path(__file__).parent.joinpath('data/ap_user.json')
data = json.loads(datafile.read_bytes())
data['id'] = self.fake_remote.remote_id
data['publicKey']['publicKeyPem'] = self.fake_remote.public_key
del data['icon'] # Avoid having to return an avatar.
responses.add(
responses.GET,
self.fake_remote.remote_id,
json=data,
status=200)
responses.add(
responses.GET,
'https://localhost/.well-known/nodeinfo',
status=404)
responses.add(
responses.GET,
'https://example.com/user/mouse/outbox?page=true',
json={'orderedItems': []},
status=200
)
response = self.send_test_request(sender=self.fake_remote)
self.assertEqual(response.status_code, 200)
@responses.activate
def test_key_needs_refresh(self):
datafile = pathlib.Path(__file__).parent.joinpath('data/ap_user.json')
data = json.loads(datafile.read_bytes())
data['id'] = self.fake_remote.remote_id
data['publicKey']['publicKeyPem'] = self.fake_remote.public_key
del data['icon'] # Avoid having to return an avatar.
responses.add(
responses.GET,
self.fake_remote.remote_id,
json=data,
status=200)
responses.add(
responses.GET,
'https://localhost/.well-known/nodeinfo',
status=404)
responses.add(
responses.GET,
'https://example.com/user/mouse/outbox?page=true',
json={'orderedItems': []},
status=200
)
# Second and subsequent fetches get a different key:
new_private_key, new_public_key = create_key_pair()
new_sender = Sender(
self.fake_remote.remote_id, new_private_key, new_public_key)
data['publicKey']['publicKeyPem'] = new_public_key
responses.add(
responses.GET,
self.fake_remote.remote_id,
json=data,
status=200)
# Key correct:
response = self.send_test_request(sender=self.fake_remote)
self.assertEqual(response.status_code, 200)
# Old key is cached, so still works:
response = self.send_test_request(sender=self.fake_remote)
self.assertEqual(response.status_code, 200)
# Try with new key:
response = self.send_test_request(sender=new_sender)
self.assertEqual(response.status_code, 200)
# Now the old key will fail:
response = self.send_test_request(sender=self.fake_remote)
self.assertEqual(response.status_code, 401)
@responses.activate
def test_nonexistent_signer(self):
responses.add(
responses.GET,
self.fake_remote.remote_id,
json={'error': 'not found'},
status=404)
response = self.send_test_request(sender=self.fake_remote)
self.assertEqual(response.status_code, 401)
def test_changed_data(self):
'''Message data must match the digest header.'''
response = self.send_test_request(
self.mouse,
send_data=get_follow_data(self.mouse, self.cat))
self.assertEqual(response.status_code, 401)
def test_invalid_digest(self):
response = self.send_test_request(
self.mouse,
digest='SHA-256=AAAAAAAAAAAAAAAAAA')
self.assertEqual(response.status_code, 401)
def test_old_message(self):
'''Old messages should be rejected to prevent replay attacks.'''
response = self.send_test_request(
self.mouse,
date=http_date(time.time() - 301)
)
self.assertEqual(response.status_code, 401)