Skip to content

Predefined Steps

In order to use these steps with behave, you have to create a file like

features/steps/import.py
from cattle_grid.testing.features.steps import *  # noqa

in your steps directory.

cattle_grid.testing.features.steps.user

actor_deletes_themselves(context, alice) async

When "Alice" deletes herself
When "Bob" deletes himself
Source code in cattle_grid/testing/features/steps/user.py
@when('"{alice}" deletes herself')
@when('"{alice}" deletes himself')
@async_run_until_complete
async def actor_deletes_themselves(context, alice):
    """
    ```gherkin
    When "Alice" deletes herself
    When "Bob" deletes himself
    ```
    """
    alice_id = context.actors[alice].get("id")

    await publish_as(
        context,
        alice,
        "delete_actor",
        {
            "actor": alice_id,
        },
    )

create_user_on_server(context, username, hostname) async

Helper routine that creates a user on the server.

The user object and Listener are stored in context.actors[username] and context.listeners[username] respectively.

Source code in cattle_grid/testing/features/steps/user.py
async def create_user_on_server(context, username, hostname) -> None:
    """Helper routine that creates a user on the server.

    The user object and Listener are stored in
    `context.actors[username]` and `context.listeners[username]`
    respectively."""
    response = await context.session.post(
        f"http://{hostname}/admin/create",
        data={"username": username, "password": username},
    )

    if response.status == 409:
        logger.warning("User already exists deleting")
        response = await context.session.post(
            "http://abel/admin/delete", data={"username": username}
        )

        assert response.status == 200

        response = await context.session.post(
            f"http://{hostname}/admin/create",
            data={"username": username, "password": username},
        )

    assert response.status == 201

    context.actors[username] = await response.json()

    context.connections[username] = Almabtrieb.from_connection_string(
        f"amqp://{username}:{username}@rabbitmq/", silent=True
    )
    context.connections[username].task = asyncio.create_task(
        context.connections[username].run()
    )

    while not context.connections[username].connected:
        await asyncio.sleep(0.1)

new_user(context, username) async

Creates a new user

Usage example:

Given A new user called "Alice"
Source code in cattle_grid/testing/features/steps/user.py
@given('A new user called "{username}"')
@async_run_until_complete
async def new_user(context, username):
    """Creates a new user

    Usage example:

    ```gherkin
    Given A new user called "Alice"
    ```
    """
    hostname = {"alice": "abel", "bob": "banach", "Bob": "banach"}.get(username, "abel")

    await create_user_on_server(context, username, hostname)

update_profile(context, alice) async

When "Alice" updates her profile
Source code in cattle_grid/testing/features/steps/user.py
@when('"{alice}" updates her profile')
@async_run_until_complete
async def update_profile(context, alice):
    """
    ```gherkin
    When "Alice" updates her profile
    ```
    """

    for connection in context.connections.values():
        await connection.clear_incoming()

    alice_id = context.actors[alice].get("id")

    msg = UpdateActorMessage(
        actor=alice_id, profile={"summary": "I love cows"}
    ).model_dump()

    await publish_as(context, alice, "update_actor", msg)

cattle_grid.testing.features.steps.follow

accept_follow_request(context, actor) async

Checks that Alice received a follow Activity and then accepts this follow activity

When "Alice" sends an Accept to this Follow Activity
Source code in cattle_grid/testing/features/steps/follow.py
@when('"{actor}" sends an Accept to this Follow Activity')
@async_run_until_complete
async def accept_follow_request(context, actor):
    """Checks that Alice received a follow Activity and then
    accepts this follow activity

    ```gherkin
    When "Alice" sends an Accept to this Follow Activity
    ```
    """
    result = await context.connections[actor].next_incoming()
    received_activity = result.get("data")
    if "raw" in received_activity:
        received_activity = received_activity["raw"]

    logger.info("Got follow request:")
    logger.info(received_activity)

    assert received_activity["type"] == "Follow"

    follow_id = received_activity["id"]
    to_follow = received_activity["actor"]

    alice = context.actors[actor]
    activity_factory, _ = factories_for_actor_object(alice)

    activity = activity_factory.accept(follow_id, to={to_follow}).build()
    activity["id"] = "accept:" + str(uuid4())

    await publish_as(
        context, actor, "send_message", send_message_as_actor(alice, activity)
    )

actor_follows_other(context, bob, alice)

Combination of two steps, i.e.

When "Alice" follows "Bob"

is the same as

When "Alice" sends "Bob" a Follow Activity
And "Bob" sends an Accept to this Follow Activity
Source code in cattle_grid/testing/features/steps/follow.py
@given('"{bob}" follows "{alice}"')
def actor_follows_other(context, bob, alice):
    """Combination of two steps, i.e.

    ```gherkin
    When "Alice" follows "Bob"
    ```

    is the same as

    ```gherkin
    When "Alice" sends "Bob" a Follow Activity
    And "Bob" sends an Accept to this Follow Activity
    ```
    """

    if alice not in context.connections:
        context.execute_steps(
            f"""
        When "{bob}" sends "{alice}" a Follow Activity
    """
        )
    else:
        context.execute_steps(
            f"""
        When "{bob}" sends "{alice}" a Follow Activity
        And "{alice}" sends an Accept to this Follow Activity
    """
        )

automatically_accept_followers(context, alice) async

FIXME: Should toggle

Source code in cattle_grid/testing/features/steps/follow.py
@given('"{alice}" automatically accepts followers')
@async_run_until_complete
async def automatically_accept_followers(context, alice):
    """FIXME: Should toggle"""

    actor = context.actors[alice]

    await publish_as(
        context,
        alice,
        "update_actor",
        {"actor": actor.get("id"), "autoFollow": True},
    )

send_follow(context, alice, bob) async

Sends a follow Activity. Usage

When "Alice" sends "Bob" a Follow Activity

Stores the follow activity in context.follow_activity

Source code in cattle_grid/testing/features/steps/follow.py
@when('"{alice}" sends "{bob}" a Follow Activity')
@async_run_until_complete
async def send_follow(context, alice, bob):
    """Sends a follow Activity. Usage

    ```gherkin
    When "Alice" sends "Bob" a Follow Activity
    ```

    Stores the follow activity in `context.follow_activity`
    """
    alice_actor = context.actors[alice]
    bob_id = context.actors[bob].get("id")
    activity_factory, _ = factories_for_actor_object(alice_actor)

    context.follow_id = "follow:" + str(uuid4())

    context.follow_activity = activity_factory.follow(
        bob_id, id=context.follow_id
    ).build()

    await publish_as(
        context,
        alice,
        "send_message",
        send_message_as_actor(alice_actor, context.follow_activity),
    )

send_reject_follow(context, alice, bob) async

Sends an Undo Follow activity for the follow activity with id stored in context.follow_activity.

Usage:

When "Alice" sends "Bob" a Reject Follow Activity
Source code in cattle_grid/testing/features/steps/follow.py
@when('"{alice}" sends "{bob}" a Reject Follow Activity')
@async_run_until_complete
async def send_reject_follow(context, alice, bob):
    """Sends an Undo Follow activity for the follow activity
    with id stored in `context.follow_activity`.

    Usage:

    ```gherkin
    When "Alice" sends "Bob" a Reject Follow Activity
    ```
    """
    actor = context.actors[alice]
    activity_factory, _ = factories_for_actor_object(actor)

    activity = activity_factory.reject(context.follow_activity).build()
    if isinstance(activity["object"], dict):
        activity["object"] = activity["object"]["id"]

    activity["id"] = "reject:" + str(uuid4())

    await publish_as(
        context, alice, "send_message", send_message_as_actor(actor, activity)
    )

send_undo_follow(context, bob, alice) async

Sends an Undo Follow activity for the follow activity with id stored in context.follow_activity.

Usage:

When "Bob" sends "Alice" an Undo Follow Activity
Source code in cattle_grid/testing/features/steps/follow.py
@when('"{bob}" sends "{alice}" an Undo Follow Activity')
@async_run_until_complete
async def send_undo_follow(context, bob, alice):
    """Sends an Undo Follow activity for the follow activity
    with id stored in `context.follow_activity`.

    Usage:

    ```gherkin
    When "Bob" sends "Alice" an Undo Follow Activity
    ```
    """
    actor = context.actors[bob]
    activity_factory, _ = factories_for_actor_object(actor)

    activity = activity_factory.undo(context.follow_activity).build()
    if isinstance(activity["object"], dict):
        activity["object"] = activity["object"]["id"]

    activity["id"] = "undo:" + str(uuid4())

    await publish_as(
        context, bob, "send_message", send_message_as_actor(actor, activity)
    )

cattle_grid.testing.features.steps.collection

check_collection(context, alice, bob, collection) async

Used to check if the followers or following collection of the actor bob does not contain the actor alice.

Then The "followers" collection of "bob" does not include "alice"
Source code in cattle_grid/testing/features/steps/collection.py
@then('The "{collection}" collection of "{bob}" does not include "{alice}"')
@async_run_until_complete
async def check_collection(context, alice, bob, collection):
    """Used to check if the followers or following collection
    of the actor `bob` does not contain the actor `alice`.

    ```gherkin
    Then The "followers" collection of "bob" does not include "alice"
    ```
    """
    result = await fetch_request(
        context,
        bob,
        context.actors[bob].get(collection),
    )

    actor = context.actors[alice].get("id")

    if "raw" in result:
        result = result["raw"]

    assert result.get("type") == "OrderedCollection"
    assert actor not in result.get("orderedItems", [])

check_collection_contains(context, alice, bob, collection) async

Used to check if the followers or following collection of the actor bob contains the actor alice.

Then The "followers" collection of "bob" contains "alice"
Source code in cattle_grid/testing/features/steps/collection.py
@then('The "{collection}" collection of "{bob}" contains "{alice}"')
@async_run_until_complete
async def check_collection_contains(context, alice, bob, collection):
    """Used to check if the followers or following collection
    of the actor `bob` contains the actor `alice`.

    ```gherkin
    Then The "followers" collection of "bob" contains "alice"
    ```
    """
    result = await fetch_request(
        context,
        bob,
        context.actors[bob].get(collection),
    )

    bob_id = context.actors[alice].get("id")

    if "raw" in result:
        result = result["raw"]

    logger.info(result)

    assert result.get("type") == "OrderedCollection"
    assert bob_id in result.get("orderedItems", [])

cattle_grid.testing.features.steps.messaging

check_activity_type(context, activity_type)

Checks that the received activity from cattle_grid.testing.features.steps.messaging.receive_activity is of type activity_type.

Then the received activity is of type "Update"
Source code in cattle_grid/testing/features/steps/messaging.py
@then('the received activity is of type "{activity_type}"')
def check_activity_type(context, activity_type):
    """Checks that the received activity from [cattle_grid.testing.features.steps.messaging.receive_activity][]
    is of type `activity_type`.

    ```gherkin
    Then the received activity is of type "Update"
    ```
    """

    received = context.activity
    if "raw" in received:
        received = received["raw"]

    import json

    print(json.dumps(received, indent=2))

    assert (
        received.get("type") == activity_type
    ), f"Activity {received} has the wrong type"

check_message(context, actor, text) async

Used to check if the last message received by actor is saying the correct thing.

Then "bob" receives a message saying "Got milk?"
Source code in cattle_grid/testing/features/steps/messaging.py
@then('"{actor}" receives a message saying "{text}"')
@async_run_until_complete
async def check_message(context, actor, text):
    """Used to check if the last message received by actor
    is saying the correct thing.

    ```gherkin
    Then "bob" receives a message saying "Got milk?"
    ```
    """

    data = await context.connections[actor].next_incoming()

    assert data.get("event_type") == "incoming"
    activity = data.get("data")

    if "raw" in activity:
        activity = activity["raw"]

    assert activity.get("type") == "Create", f"got {activity}"
    assert activity.get("@context"), f"got {activity}"

    obj = activity.get("object", {})
    assert obj.get("content") == text, f"""got {obj.get("content")}"""

    context.received_object = obj

not_receive_activity(context, actor) async

Ensures that no incoming activity was received

Then "bob" does not receive an activity
Source code in cattle_grid/testing/features/steps/messaging.py
@then('"{actor}" does not receive an activity')
@async_run_until_complete
async def not_receive_activity(context, actor):
    """Ensures that no incoming activity was received

    ```gherkin
    Then "bob" does not receive an activity
    ```
    """

    try:
        result = await context.connections[actor].next_incoming()

        assert result is None, f"Received activity {json.dumps(result.data)}"
    except NoIncomingException:
        ...

receive_activity(context, actor) async

Ensures that an incoming activity was received and stores it in context.activity.

Then "bob" receives an activity
Source code in cattle_grid/testing/features/steps/messaging.py
@then('"{actor}" receives an activity')
@async_run_until_complete
async def receive_activity(context, actor):
    """Ensures that an incoming activity was received
    and stores it in `context.activity`.

    ```gherkin
    Then "bob" receives an activity
    ```
    """

    data = await context.connections[actor].next_incoming()
    assert data.get("event_type") == "incoming"

    context.activity = data["data"]["raw"]

    assert context.activity["@context"]

send_message(context, actor, target, text) async

Used to send a message. The message has the format (with a lot of stuff omitted)

{
    "type": "Create",
    "object": {
        "type": "Note",
        "content": text,
        "to": [actor_id_of_target]
    }
}

This step can be used as

When "alice" sends "bob" a message saying "You stole my milk!"
Source code in cattle_grid/testing/features/steps/messaging.py
@when('"{actor}" sends "{target}" a message saying "{text}"')
@async_run_until_complete
async def send_message(context, actor, target, text):
    """Used to send a message. The message has the format (with a lot of stuff omitted)

    ```json
    {
        "type": "Create",
        "object": {
            "type": "Note",
            "content": text,
            "to": [actor_id_of_target]
        }
    }
    ```

    This step can be used as

    ```gherkin
    When "alice" sends "bob" a message saying "You stole my milk!"
    ```
    """
    alice = context.actors[actor]
    activity_factory, object_factory = factories_for_actor_object(
        alice, id_generator=id_generator_for_actor(alice)
    )

    bob_id = context.actors[target].get("id")

    note = object_factory.note(content=text, to={bob_id}).build()
    activity = activity_factory.create(note).build()

    await publish_as(
        context,
        actor,
        "send_message",
        send_message_as_actor(alice, activity),
    )

send_message_followers(context, actor, text) async

Used to send a message to the followers. The message has the format (with a lot of stuff omitted)

{
    "type": "Create",
    "object": {
        "type": "Note",
        "content": text,
        "to": [followers_collection_of_actor]
    }
}

This step can be used as

When "alice" messages her followers "Got milk?"
Source code in cattle_grid/testing/features/steps/messaging.py
@when('"{actor}" messages her followers "{text}"')
@async_run_until_complete
async def send_message_followers(context, actor, text):
    """Used to send a message to the followers. The message has the format (with a lot of stuff omitted)

    ```json
    {
        "type": "Create",
        "object": {
            "type": "Note",
            "content": text,
            "to": [followers_collection_of_actor]
        }
    }
    ```

    This step can be used as

    ```gherkin
    When "alice" messages her followers "Got milk?"
    ```
    """
    for connection in context.connections.values():
        await connection.clear_incoming()

    alice = context.actors[actor]

    activity_factory, object_factory = factories_for_actor_object(
        alice, id_generator=id_generator_for_actor(alice)
    )
    note = object_factory.note(content=text).as_followers().build()
    activity = activity_factory.create(note).build()

    await publish_as(
        context,
        actor,
        "send_message",
        send_message_as_actor(alice, activity),
    )