async def accepts_type_definition_with_async_subscribe_function(): pubsub = SimplePubSub() async def get_subscriber(*_args): await asyncio.sleep(0) return pubsub.get_subscriber() schema = GraphQLSchema( query=QueryType, subscription=GraphQLObjectType( "Subscription", { "importantEmail": GraphQLField( GraphQLString, subscribe=get_subscriber ) }, ), ) subscription = await subscribe( schema, parse( """ subscription { importantEmail } """ ), ) pubsub.emit({"importantEmail": {}}) await anext(subscription)
async def produces_a_payload_for_multiple_subscribe_in_same_subscription(): pubsub = SimplePubSub() subscription = await create_subscription(pubsub) assert isinstance(subscription, MapAsyncIterator) second_subscription = await create_subscription(pubsub) assert isinstance(subscription, MapAsyncIterator) payload1 = anext(subscription) payload2 = anext(second_subscription) assert (pubsub.emit({ "from": "*****@*****.**", "subject": "Alright", "message": "Tests are good", "unread": True, }) is True) expected_payload = { "importantEmail": { "email": { "from": "*****@*****.**", "subject": "Alright" }, "inbox": { "unread": 1, "total": 2 }, } } assert await payload1 == (expected_payload, None) assert await payload2 == (expected_payload, None)
async def accepts_multiple_subscription_fields_defined_in_schema(): pubsub = SimplePubSub() subscription_type_multiple = GraphQLObjectType( "Subscription", { "importantEmail": GraphQLField(EmailEventType), "nonImportantEmail": GraphQLField(EmailEventType), }, ) test_schema = GraphQLSchema( query=QueryType, subscription=subscription_type_multiple ) subscription = await create_subscription(pubsub, test_schema) assert isinstance(subscription, MapAsyncIterator) pubsub.emit( { "from": "*****@*****.**", "subject": "Alright", "message": "Tests are good", "unread": True, } ) await anext(subscription)
async def iterator_aclose_is_idempotent(): pubsub = SimplePubSub() iterator = pubsub.get_subscriber() assert iterator.listening for n in range(3): await iterator.aclose() assert not iterator.listening
async def event_order_is_correct_for_multiple_publishes(): pubsub = SimplePubSub() subscription = await create_subscription(pubsub) assert isinstance(subscription, MapAsyncIterator) payload = anext(subscription) # A new email arrives! assert (pubsub.emit({ "from": "*****@*****.**", "subject": "Message", "message": "Tests are good", "unread": True, }) is True) # A new email arrives! assert (pubsub.emit({ "from": "*****@*****.**", "subject": "Message 2", "message": "Tests are good 2", "unread": True, }) is True) assert await payload == ( { "importantEmail": { "email": { "from": "*****@*****.**", "subject": "Message" }, "inbox": { "unread": 2, "total": 3 }, } }, None, ) payload = anext(subscription) assert await payload == ( { "importantEmail": { "email": { "from": "*****@*****.**", "subject": "Message 2" }, "inbox": { "unread": 2, "total": 3 }, } }, None, )
async def produces_a_payload_when_there_are_multiple_events(): pubsub = SimplePubSub() subscription = await create_subscription(pubsub) assert isinstance(subscription, MapAsyncIterator) payload = anext(subscription) # A new email arrives! assert (pubsub.emit({ "from": "*****@*****.**", "subject": "Alright", "message": "Tests are good", "unread": True, }) is True) assert await payload == ( { "importantEmail": { "email": { "from": "*****@*****.**", "subject": "Alright" }, "inbox": { "unread": 1, "total": 2 }, } }, None, ) payload = anext(subscription) # A new email arrives! assert (pubsub.emit({ "from": "*****@*****.**", "subject": "Alright 2", "message": "Tests are good 2", "unread": True, }) is True) assert await payload == ( { "importantEmail": { "email": { "from": "*****@*****.**", "subject": "Alright 2" }, "inbox": { "unread": 2, "total": 3 }, } }, None, )
async def should_not_trigger_when_subscription_is_thrown(): pubsub = SimplePubSub() subscription = await create_subscription(pubsub) assert isinstance(subscription, MapAsyncIterator) payload = anext(subscription) # A new email arrives! assert ( pubsub.emit( { "from": "*****@*****.**", "subject": "Alright", "message": "Tests are good", "unread": True, } ) is True ) assert await payload == ( { "importantEmail": { "email": {"from": "*****@*****.**", "subject": "Alright"}, "inbox": {"unread": 1, "total": 2}, } }, None, ) payload = anext(subscription) # Throw error with raises(RuntimeError) as exc_info: await subscription.athrow(RuntimeError("ouch")) assert str(exc_info.value) == "ouch" # A new email arrives! assert ( pubsub.emit( { "from": "*****@*****.**", "subject": "Alright 2", "message": "Tests are good 2", "unread": True, } ) is False ) with raises(StopAsyncIteration): await payload
async def iterator_aclose_empties_pull_queue(): pubsub = SimplePubSub() assert not pubsub.subscribers iterator = pubsub.get_subscriber() assert len(pubsub.subscribers) == 1 assert iterator.listening for _n in range(3): await iterator.__anext__() assert iterator.push_queue.qsize() == 0 assert iterator.pull_queue.qsize() == 3 await iterator.aclose() assert not pubsub.subscribers assert iterator.push_queue.qsize() == 0 assert iterator.pull_queue.qsize() == 0 assert not iterator.listening
async def should_not_trigger_when_subscription_is_already_done(): pubsub = SimplePubSub() subscription = await create_subscription(pubsub) assert isinstance(subscription, MapAsyncIterator) payload = anext(subscription) # A new email arrives! assert ( pubsub.emit( { "from": "*****@*****.**", "subject": "Alright", "message": "Tests are good", "unread": True, } ) is True ) assert await payload == ( { "importantEmail": { "email": {"from": "*****@*****.**", "subject": "Alright"}, "inbox": {"unread": 1, "total": 2}, } }, None, ) payload = anext(subscription) await subscription.aclose() # A new email arrives! assert ( pubsub.emit( { "from": "*****@*****.**", "subject": "Alright 2", "message": "Tests are good 2", "unread": True, } ) is False ) with raises(StopAsyncIteration): await payload
async def iterator_aclose_empties_push_queue(): pubsub = SimplePubSub() assert not pubsub.subscribers iterator = pubsub.get_subscriber() assert len(pubsub.subscribers) == 1 assert iterator.listening for value in range(3): pubsub.emit(value) await sleep(0) assert iterator.push_queue.qsize() == 3 assert iterator.pull_queue.qsize() == 0 await iterator.aclose() assert not pubsub.subscribers assert iterator.push_queue.qsize() == 0 assert iterator.pull_queue.qsize() == 0 assert not iterator.listening
async def subscribe_async_iterator_mock(): pubsub = SimplePubSub() iterator = pubsub.get_subscriber() # Queue up publishes assert pubsub.emit("Apple") is True assert pubsub.emit("Banana") is True # Read payloads assert await iterator.__anext__() == "Apple" assert await iterator.__anext__() == "Banana" # Read ahead i3 = await iterator.__anext__() assert isawaitable(i3) i4 = await iterator.__anext__() assert isawaitable(i4) # Publish assert pubsub.emit("Coconut") is True assert pubsub.emit("Durian") is True # Await out of order to get correct results assert await i4 == "Durian" assert await i3 == "Coconut" # Read ahead i5 = iterator.__anext__() # Terminate queue await iterator.aclose() # Publish is not caught after terminate assert pubsub.emit("Fig") is False # Find that cancelled read-ahead got a "done" result with raises(StopAsyncIteration): await i5 # And next returns empty completion value with raises(StopAsyncIteration): await iterator.__anext__()
async def throws_an_error_if_subscribe_does_not_return_an_iterator(): invalid_email_schema = GraphQLSchema( query=QueryType, subscription=GraphQLObjectType( "Subscription", { "importantEmail": GraphQLField( GraphQLString, subscribe=lambda _inbox, _info: "test" ) }, ), ) pubsub = SimplePubSub() with raises(TypeError) as exc_info: await create_subscription(pubsub, invalid_email_schema) assert str(exc_info.value) == ( "Subscription field must return AsyncIterable. Received: 'test'." )
async def resolves_to_an_error_for_unknown_subscription_field(): ast = parse( """ subscription { unknownField } """ ) pubsub = SimplePubSub() subscription = await create_subscription(pubsub, email_schema, ast) assert subscription == ( None, [ { "message": "The subscription field 'unknownField' is not defined.", "locations": [(3, 15)], } ], )
async def produces_a_payload_per_subscription_event(): pubsub = SimplePubSub() subscription = await create_subscription(pubsub) assert isinstance(subscription, MapAsyncIterator) # Wait for the next subscription payload. payload = anext(subscription) # A new email arrives! assert (pubsub.emit({ "from": "*****@*****.**", "subject": "Alright", "message": "Tests are good", "unread": True, }) is True) # The previously waited on payload now has a value. assert await payload == ( { "importantEmail": { "email": { "from": "*****@*****.**", "subject": "Alright" }, "inbox": { "unread": 1, "total": 2 }, } }, None, ) # Another new email arrives, before anext(subscription) is called. assert (pubsub.emit({ "from": "*****@*****.**", "subject": "Tools", "message": "I <3 making things", "unread": True, }) is True) # The next waited on payload will have a value. assert await anext(subscription) == ( { "importantEmail": { "email": { "from": "*****@*****.**", "subject": "Tools" }, "inbox": { "unread": 2, "total": 3 }, } }, None, ) # The client decides to disconnect. # noinspection PyUnresolvedReferences await subscription.aclose() # Which may result in disconnecting upstream services as well. assert (pubsub.emit({ "from": "*****@*****.**", "subject": "Important", "message": "Read me please", "unread": True, }) is False) # No more listeners. # Awaiting subscription after closing it results in completed results. with raises(StopAsyncIteration): assert await anext(subscription)
def subscribe_non_important(*_args): # pragma: no cover did_resolve["nonImportantEmail"] = True return SimplePubSub().get_subscriber()
def subscribe_important(*_args): did_resolve["importantEmail"] = True return SimplePubSub().get_subscriber()