448 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			448 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
| import pytest
 | |
| from rest_framework import serializers
 | |
| 
 | |
| from funkwhale_api.federation import contexts, jsonld
 | |
| 
 | |
| 
 | |
| def test_expand_no_external_request():
 | |
|     payload = {
 | |
|         "id": "https://noop/federation/actors/demo",
 | |
|         "outbox": "https://noop/federation/actors/demo/outbox",
 | |
|         "inbox": "https://noop/federation/actors/demo/inbox",
 | |
|         "preferredUsername": "demo",
 | |
|         "type": "Person",
 | |
|         "name": "demo",
 | |
|         "followers": "https://noop/federation/actors/demo/followers",
 | |
|         "following": "https://noop/federation/actors/demo/following",
 | |
|         "manuallyApprovesFollowers": False,
 | |
|         "@context": [
 | |
|             "https://www.w3.org/ns/activitystreams",
 | |
|             "https://w3id.org/security/v1",
 | |
|             {},
 | |
|         ],
 | |
|         "publicKey": {
 | |
|             "owner": "https://noop/federation/actors/demo",
 | |
|             "publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxPDd/oXx0ClJ2BuBZ937AiERjvoroEpNebg34Cdl6FYsb2Auib8b\nCQjdjLjK/1ag35lmqmsECqtoDYWOo4tGilZJW47TWmXfcvCMH2Sw9FqdOlzpV1RI\nm8kc0Lu1CC2xOTctqIwSH7kDDnS4+S5hSxRdMTeNQNoirncY1CXa9TmJR1lE2HWz\n+B05ewzMrSen3l3fJLQFoI2GVbbjj+tvILKBL1oG5MtYieYqjt2sqtqy/OpWUAC7\nlRERRzd4t5xPBKykWkBCAOh80pvPue5V4s+xUMr7ioKTcm6pq+pNBta5w0hUYIcT\nMefQOnNuR4J0meIqiDLcrglGAmM6AVFwYwIDAQAB\n-----END RSA PUBLIC KEY-----\n",  # noqa
 | |
|             "id": "https://noop/federation/actors/demo#main-key",
 | |
|         },
 | |
|         "endpoints": {"sharedInbox": "https://noop/federation/shared/inbox"},
 | |
|     }
 | |
| 
 | |
|     expected = {
 | |
|         contexts.AS.endpoints: [
 | |
|             {contexts.AS.sharedInbox: [{"@id": "https://noop/federation/shared/inbox"}]}
 | |
|         ],
 | |
|         contexts.AS.followers: [
 | |
|             {"@id": "https://noop/federation/actors/demo/followers"}
 | |
|         ],
 | |
|         contexts.AS.following: [
 | |
|             {"@id": "https://noop/federation/actors/demo/following"}
 | |
|         ],
 | |
|         "@id": "https://noop/federation/actors/demo",
 | |
|         "http://www.w3.org/ns/ldp#inbox": [
 | |
|             {"@id": "https://noop/federation/actors/demo/inbox"}
 | |
|         ],
 | |
|         contexts.AS.manuallyApprovesFollowers: [{"@value": False}],
 | |
|         contexts.AS.name: [{"@value": "demo"}],
 | |
|         contexts.AS.outbox: [{"@id": "https://noop/federation/actors/demo/outbox"}],
 | |
|         contexts.AS.preferredUsername: [{"@value": "demo"}],
 | |
|         contexts.SEC.publicKey: [
 | |
|             {
 | |
|                 "@id": "https://noop/federation/actors/demo#main-key",
 | |
|                 contexts.SEC.owner: [{"@id": "https://noop/federation/actors/demo"}],
 | |
|                 contexts.SEC.publicKeyPem: [
 | |
|                     {
 | |
|                         "@value": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxPDd/oXx0ClJ2BuBZ937AiERjvoroEpNebg34Cdl6FYsb2Auib8b\nCQjdjLjK/1ag35lmqmsECqtoDYWOo4tGilZJW47TWmXfcvCMH2Sw9FqdOlzpV1RI\nm8kc0Lu1CC2xOTctqIwSH7kDDnS4+S5hSxRdMTeNQNoirncY1CXa9TmJR1lE2HWz\n+B05ewzMrSen3l3fJLQFoI2GVbbjj+tvILKBL1oG5MtYieYqjt2sqtqy/OpWUAC7\nlRERRzd4t5xPBKykWkBCAOh80pvPue5V4s+xUMr7ioKTcm6pq+pNBta5w0hUYIcT\nMefQOnNuR4J0meIqiDLcrglGAmM6AVFwYwIDAQAB\n-----END RSA PUBLIC KEY-----\n"  # noqa
 | |
|                     }
 | |
|                 ],
 | |
|             }
 | |
|         ],
 | |
|         "@type": [contexts.AS.Person],
 | |
|     }
 | |
| 
 | |
|     doc = jsonld.expand(payload)
 | |
| 
 | |
|     assert doc == expected
 | |
| 
 | |
| 
 | |
| def test_expand_no_external_request_pleroma():
 | |
|     payload = {
 | |
|         "@context": [
 | |
|             "https://www.w3.org/ns/activitystreams",
 | |
|             "https://pleroma.example/schemas/litepub-0.1.jsonld",
 | |
|             {"@language": "und"},
 | |
|         ],
 | |
|         "endpoints": {
 | |
|             "oauthAuthorizationEndpoint": "https://pleroma.example/oauth/authorize",
 | |
|             "oauthRegistrationEndpoint": "https://pleroma.example/api/v1/apps",
 | |
|             "oauthTokenEndpoint": "https://pleroma.example/oauth/token",
 | |
|             "sharedInbox": "https://pleroma.example/inbox",
 | |
|             "uploadMedia": "https://pleroma.example/api/ap/upload_media",
 | |
|         },
 | |
|         "followers": "https://pleroma.example/internal/fetch/followers",
 | |
|         "following": "https://pleroma.example/internal/fetch/following",
 | |
|         "id": "https://pleroma.example/internal/fetch",
 | |
|         "inbox": "https://pleroma.example/internal/fetch/inbox",
 | |
|         "invisible": True,
 | |
|         "manuallyApprovesFollowers": False,
 | |
|         "name": "Pleroma",
 | |
|         "preferredUsername": "internal.fetch",
 | |
|         "publicKey": {
 | |
|             "id": "https://pleroma.example/internal/fetch#main-key",
 | |
|             "owner": "https://pleroma.example/internal/fetch",
 | |
|             "publicKeyPem": "PEM",
 | |
|         },
 | |
|         "summary": "An internal service actor for this Pleroma instance.  No user-serviceable parts inside.",
 | |
|         "type": "Application",
 | |
|         "url": "https://pleroma.example/internal/fetch",
 | |
|     }
 | |
| 
 | |
|     expected = {
 | |
|         contexts.AS.endpoints: [
 | |
|             {
 | |
|                 contexts.AS.sharedInbox: [{"@id": "https://pleroma.example/inbox"}],
 | |
|                 contexts.AS.oauthAuthorizationEndpoint: [
 | |
|                     {"@id": "https://pleroma.example/oauth/authorize"}
 | |
|                 ],
 | |
|                 contexts.LITEPUB.oauthRegistrationEndpoint: [
 | |
|                     {"@id": "https://pleroma.example/api/v1/apps"}
 | |
|                 ],
 | |
|                 contexts.AS.oauthTokenEndpoint: [
 | |
|                     {"@id": "https://pleroma.example/oauth/token"}
 | |
|                 ],
 | |
|                 contexts.AS.uploadMedia: [
 | |
|                     {"@id": "https://pleroma.example/api/ap/upload_media"}
 | |
|                 ],
 | |
|             },
 | |
|         ],
 | |
|         contexts.AS.followers: [
 | |
|             {"@id": "https://pleroma.example/internal/fetch/followers"}
 | |
|         ],
 | |
|         contexts.AS.following: [
 | |
|             {"@id": "https://pleroma.example/internal/fetch/following"}
 | |
|         ],
 | |
|         "@id": "https://pleroma.example/internal/fetch",
 | |
|         "http://www.w3.org/ns/ldp#inbox": [
 | |
|             {"@id": "https://pleroma.example/internal/fetch/inbox"}
 | |
|         ],
 | |
|         contexts.LITEPUB.invisible: [{"@value": True}],
 | |
|         contexts.AS.manuallyApprovesFollowers: [{"@value": False}],
 | |
|         contexts.AS.name: [{"@language": "und", "@value": "Pleroma"}],
 | |
|         contexts.AS.summary: [
 | |
|             {
 | |
|                 "@language": "und",
 | |
|                 "@value": "An internal service actor for this Pleroma instance.  No user-serviceable parts inside.",
 | |
|             }
 | |
|         ],
 | |
|         contexts.AS.url: [{"@id": "https://pleroma.example/internal/fetch"}],
 | |
|         contexts.AS.preferredUsername: [
 | |
|             {"@language": "und", "@value": "internal.fetch"}
 | |
|         ],
 | |
|         contexts.SEC.publicKey: [
 | |
|             {
 | |
|                 "@id": "https://pleroma.example/internal/fetch#main-key",
 | |
|                 contexts.SEC.owner: [{"@id": "https://pleroma.example/internal/fetch"}],
 | |
|                 contexts.SEC.publicKeyPem: [{"@language": "und", "@value": "PEM"}],
 | |
|             }
 | |
|         ],
 | |
|         "@type": [contexts.AS.Application],
 | |
|     }
 | |
| 
 | |
|     doc = jsonld.expand(payload)
 | |
| 
 | |
|     assert doc[contexts.AS.endpoints] == expected[contexts.AS.endpoints]
 | |
|     assert doc == expected
 | |
| 
 | |
| 
 | |
| def test_expand_remote_doc(r_mock):
 | |
|     url = "https://noop/federation/actors/demo"
 | |
|     payload = {
 | |
|         "id": url,
 | |
|         "outbox": "https://noop/federation/actors/demo/outbox",
 | |
|         "inbox": "https://noop/federation/actors/demo/inbox",
 | |
|         "preferredUsername": "demo",
 | |
|         "type": "Person",
 | |
|         "name": "demo",
 | |
|         "followers": "https://noop/federation/actors/demo/followers",
 | |
|         "following": "https://noop/federation/actors/demo/following",
 | |
|         "manuallyApprovesFollowers": False,
 | |
|         "@context": [
 | |
|             "https://www.w3.org/ns/activitystreams",
 | |
|             "https://w3id.org/security/v1",
 | |
|             {},
 | |
|         ],
 | |
|         "publicKey": {
 | |
|             "owner": "https://noop/federation/actors/demo",
 | |
|             "publicKeyPem": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxPDd/oXx0ClJ2BuBZ937AiERjvoroEpNebg34Cdl6FYsb2Auib8b\nCQjdjLjK/1ag35lmqmsECqtoDYWOo4tGilZJW47TWmXfcvCMH2Sw9FqdOlzpV1RI\nm8kc0Lu1CC2xOTctqIwSH7kDDnS4+S5hSxRdMTeNQNoirncY1CXa9TmJR1lE2HWz\n+B05ewzMrSen3l3fJLQFoI2GVbbjj+tvILKBL1oG5MtYieYqjt2sqtqy/OpWUAC7\nlRERRzd4t5xPBKykWkBCAOh80pvPue5V4s+xUMr7ioKTcm6pq+pNBta5w0hUYIcT\nMefQOnNuR4J0meIqiDLcrglGAmM6AVFwYwIDAQAB\n-----END RSA PUBLIC KEY-----\n",  # noqa
 | |
|             "id": "https://noop/federation/actors/demo#main-key",
 | |
|         },
 | |
|         "endpoints": {"sharedInbox": "https://noop/federation/shared/inbox"},
 | |
|     }
 | |
|     r_mock.get(url, json=payload)
 | |
| 
 | |
|     expected = {
 | |
|         contexts.AS.endpoints: [
 | |
|             {contexts.AS.sharedInbox: [{"@id": "https://noop/federation/shared/inbox"}]}
 | |
|         ],
 | |
|         contexts.AS.followers: [
 | |
|             {"@id": "https://noop/federation/actors/demo/followers"}
 | |
|         ],
 | |
|         contexts.AS.following: [
 | |
|             {"@id": "https://noop/federation/actors/demo/following"}
 | |
|         ],
 | |
|         "@id": "https://noop/federation/actors/demo",
 | |
|         "http://www.w3.org/ns/ldp#inbox": [
 | |
|             {"@id": "https://noop/federation/actors/demo/inbox"}
 | |
|         ],
 | |
|         contexts.AS.manuallyApprovesFollowers: [{"@value": False}],
 | |
|         contexts.AS.name: [{"@value": "demo"}],
 | |
|         contexts.AS.outbox: [{"@id": "https://noop/federation/actors/demo/outbox"}],
 | |
|         contexts.AS.preferredUsername: [{"@value": "demo"}],
 | |
|         contexts.SEC.publicKey: [
 | |
|             {
 | |
|                 "@id": "https://noop/federation/actors/demo#main-key",
 | |
|                 contexts.SEC.owner: [{"@id": "https://noop/federation/actors/demo"}],
 | |
|                 contexts.SEC.publicKeyPem: [
 | |
|                     {
 | |
|                         "@value": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxPDd/oXx0ClJ2BuBZ937AiERjvoroEpNebg34Cdl6FYsb2Auib8b\nCQjdjLjK/1ag35lmqmsECqtoDYWOo4tGilZJW47TWmXfcvCMH2Sw9FqdOlzpV1RI\nm8kc0Lu1CC2xOTctqIwSH7kDDnS4+S5hSxRdMTeNQNoirncY1CXa9TmJR1lE2HWz\n+B05ewzMrSen3l3fJLQFoI2GVbbjj+tvILKBL1oG5MtYieYqjt2sqtqy/OpWUAC7\nlRERRzd4t5xPBKykWkBCAOh80pvPue5V4s+xUMr7ioKTcm6pq+pNBta5w0hUYIcT\nMefQOnNuR4J0meIqiDLcrglGAmM6AVFwYwIDAQAB\n-----END RSA PUBLIC KEY-----\n"  # noqa
 | |
|                     }
 | |
|                 ],
 | |
|             }
 | |
|         ],
 | |
|         "@type": [contexts.AS.Person],
 | |
|     }
 | |
| 
 | |
|     doc = jsonld.expand(url)
 | |
| 
 | |
|     assert doc == expected
 | |
| 
 | |
| 
 | |
| async def test_fetch_many(a_responses):
 | |
|     doc = {
 | |
|         "@context": jsonld.get_default_context(),
 | |
|         "id": "https://noop/federation/actors/demo",
 | |
|         "type": "Person",
 | |
|         "followers": "https://noop/federation/actors/demo/followers",
 | |
|     }
 | |
|     followers_doc = {
 | |
|         "@context": jsonld.get_default_context(),
 | |
|         "id": "https://noop/federation/actors/demo/followers",
 | |
|         "type": "Collection",
 | |
|     }
 | |
| 
 | |
|     a_responses.get(doc["id"], payload=doc)
 | |
|     a_responses.get(followers_doc["id"], payload=followers_doc)
 | |
|     fetched = await jsonld.fetch_many(doc["id"], followers_doc["id"])
 | |
|     assert fetched == {followers_doc["id"]: followers_doc, doc["id"]: doc}
 | |
| 
 | |
| 
 | |
| def test_dereference():
 | |
|     followers_doc = {
 | |
|         "@context": jsonld.get_default_context(),
 | |
|         "id": "https://noop/federation/actors/demo/followers",
 | |
|         "type": "Collection",
 | |
|     }
 | |
| 
 | |
|     actor_doc = {
 | |
|         "@context": jsonld.get_default_context(),
 | |
|         "id": "https://noop/federation/actors/demo",
 | |
|         "type": "Person",
 | |
|         "followers": "https://noop/federation/actors/demo/followers",
 | |
|     }
 | |
| 
 | |
|     store = {followers_doc["id"]: followers_doc, actor_doc["id"]: actor_doc}
 | |
| 
 | |
|     payload = {
 | |
|         "followers": {"@id": followers_doc["id"]},
 | |
|         "actor": [
 | |
|             {"@id": actor_doc["id"], "hello": "world"},
 | |
|             {"somethingElse": [{"@id": actor_doc["id"]}]},
 | |
|         ],
 | |
|     }
 | |
|     expected = {
 | |
|         "followers": followers_doc,
 | |
|         "actor": [actor_doc, {"somethingElse": [actor_doc]}],
 | |
|     }
 | |
| 
 | |
|     assert jsonld.dereference(payload, store) == expected
 | |
| 
 | |
| 
 | |
| def test_prepare_for_serializer():
 | |
|     config = {
 | |
|         "followers": {
 | |
|             "property": contexts.AS.followers,
 | |
|             "keep": "first",
 | |
|             "attr": "@id",
 | |
|         },
 | |
|         "name": {"property": contexts.AS.name, "keep": "first", "attr": "@value"},
 | |
|         "keys": {"property": contexts.SEC.publicKey, "type": "raw"},
 | |
|     }
 | |
| 
 | |
|     payload = {
 | |
|         "@id": "https://noop/federation/actors/demo",
 | |
|         "@type": [contexts.AS.Person],
 | |
|         contexts.AS.followers: [
 | |
|             {"@id": "https://noop/federation/actors/demo/followers"}
 | |
|         ],
 | |
|         contexts.AS.name: [{"@value": "demo"}],
 | |
|         contexts.SEC.publicKey: [
 | |
|             {"@id": "https://noop/federation/actors/demo#main-key1"},
 | |
|             {"@id": "https://noop/federation/actors/demo#main-key2"},
 | |
|         ],
 | |
|     }
 | |
| 
 | |
|     expected = {
 | |
|         "id": "https://noop/federation/actors/demo",
 | |
|         "type": contexts.AS.Person,
 | |
|         "followers": "https://noop/federation/actors/demo/followers",
 | |
|         "name": "demo",
 | |
|         "keys": [
 | |
|             {"@id": "https://noop/federation/actors/demo#main-key1"},
 | |
|             {"@id": "https://noop/federation/actors/demo#main-key2"},
 | |
|         ],
 | |
|     }
 | |
| 
 | |
|     assert jsonld.prepare_for_serializer(payload, config) == expected
 | |
| 
 | |
| 
 | |
| def test_prepare_for_serializer_fallback():
 | |
|     config = {
 | |
|         "name": {"property": contexts.AS.name, "keep": "first", "attr": "@value"},
 | |
|         "album": {"property": contexts.FW.Album, "keep": "first"},
 | |
|         "noop_album": {"property": contexts.NOOP.Album, "keep": "first"},
 | |
|     }
 | |
|     fallbacks = {"album": ["noop_album"]}
 | |
| 
 | |
|     payload = {
 | |
|         "@id": "https://noop/federation/actors/demo",
 | |
|         "@type": [contexts.AS.Person],
 | |
|         contexts.AS.name: [{"@value": "demo"}],
 | |
|         contexts.NOOP.Album: [{"@id": "https://noop/federation/album/demo"}],
 | |
|     }
 | |
| 
 | |
|     expected = {
 | |
|         "id": "https://noop/federation/actors/demo",
 | |
|         "type": contexts.AS.Person,
 | |
|         "name": "demo",
 | |
|         "album": {"@id": "https://noop/federation/album/demo"},
 | |
|         "noop_album": {"@id": "https://noop/federation/album/demo"},
 | |
|     }
 | |
| 
 | |
|     assert (
 | |
|         jsonld.prepare_for_serializer(payload, config, fallbacks=fallbacks) == expected
 | |
|     )
 | |
| 
 | |
| 
 | |
| def test_jsonld_serializer_fallback():
 | |
|     class JsonLdSerializerFixture(jsonld.JsonLdSerializer):
 | |
|         id = serializers.URLField()
 | |
|         type = serializers.CharField()
 | |
|         name = serializers.CharField()
 | |
|         username = serializers.CharField()
 | |
|         total = serializers.IntegerField()
 | |
| 
 | |
|         class Meta:
 | |
|             jsonld_fallbacks = {"total": ["total_fallback"]}
 | |
|             jsonld_mapping = {
 | |
|                 "name": {
 | |
|                     "property": contexts.AS.name,
 | |
|                     "keep": "first",
 | |
|                     "attr": "@value",
 | |
|                 },
 | |
|                 "username": {
 | |
|                     "property": contexts.AS.preferredUsername,
 | |
|                     "keep": "first",
 | |
|                     "attr": "@value",
 | |
|                 },
 | |
|                 "total": {
 | |
|                     "property": contexts.AS.totalItems,
 | |
|                     "keep": "first",
 | |
|                     "attr": "@value",
 | |
|                 },
 | |
|                 "total_fallback": {
 | |
|                     "property": contexts.NOOP.count,
 | |
|                     "keep": "first",
 | |
|                     "attr": "@value",
 | |
|                 },
 | |
|             }
 | |
| 
 | |
|     payload = {
 | |
|         "@context": jsonld.get_default_context(),
 | |
|         "id": "https://noop.url/federation/actors/demo",
 | |
|         "type": "Person",
 | |
|         "name": "Hello",
 | |
|         "preferredUsername": "World",
 | |
|         "count": 42,
 | |
|     }
 | |
| 
 | |
|     serializer = JsonLdSerializerFixture(data=payload)
 | |
|     assert serializer.is_valid(raise_exception=True)
 | |
| 
 | |
|     assert serializer.validated_data == {
 | |
|         "type": contexts.AS.Person,
 | |
|         "id": payload["id"],
 | |
|         "name": payload["name"],
 | |
|         "username": payload["preferredUsername"],
 | |
|         "total": 42,
 | |
|     }
 | |
| 
 | |
| 
 | |
| def test_jsonld_serializer_dereference(a_responses):
 | |
|     class JsonLdSerializerFixture(jsonld.JsonLdSerializer):
 | |
|         id = serializers.URLField()
 | |
|         type = serializers.CharField()
 | |
|         followers = serializers.JSONField()
 | |
| 
 | |
|         class Meta:
 | |
|             jsonld_mapping = {
 | |
|                 "followers": {"property": contexts.AS.followers, "dereference": True}
 | |
|             }
 | |
| 
 | |
|     payload = {
 | |
|         "@context": jsonld.get_default_context(),
 | |
|         "id": "https://noop.url/federation/actors/demo",
 | |
|         "type": "Person",
 | |
|         "followers": "https://noop.url/federation/actors/demo/followers",
 | |
|     }
 | |
| 
 | |
|     followers_doc = {
 | |
|         "@context": jsonld.get_default_context(),
 | |
|         "id": "https://noop.url/federation/actors/demo/followers",
 | |
|         "type": "Collection",
 | |
|     }
 | |
| 
 | |
|     a_responses.get(followers_doc["id"], payload=followers_doc)
 | |
|     serializer = JsonLdSerializerFixture(data=payload)
 | |
| 
 | |
|     assert serializer.is_valid(raise_exception=True)
 | |
|     assert serializer.validated_data == {
 | |
|         "type": contexts.AS.Person,
 | |
|         "id": payload["id"],
 | |
|         "followers": [followers_doc],
 | |
|     }
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize(
 | |
|     "doc, ctx, expected",
 | |
|     [
 | |
|         (
 | |
|             {"@context": [{}], "hello": "world"},
 | |
|             "http://test",
 | |
|             {"@context": [{}, "http://test"], "hello": "world"},
 | |
|         ),
 | |
|         (
 | |
|             {"@context": {"key": "value"}, "hello": "world"},
 | |
|             "http://test",
 | |
|             {"@context": [{"key": "value"}, "http://test"], "hello": "world"},
 | |
|         ),
 | |
|         (
 | |
|             {"@context": "http://as", "hello": "world"},
 | |
|             "http://test",
 | |
|             {"@context": ["http://as", "http://test"], "hello": "world"},
 | |
|         ),
 | |
|     ],
 | |
| )
 | |
| def test_insert_context(doc, ctx, expected):
 | |
|     jsonld.insert_context(ctx, doc)
 | |
|     assert doc == expected
 |