Under one condition

Use conditions for actions and transitions.

The previous tutorial had our two actors introducing themselves. In that scenario we defined an action and a state for each actor. When scenarios becomes more complex and the number of actors increase, having to define actions and states per actor quickly adds up.

Create a new subdirectory named condition .

Since both actors need to perform the same action, we can reduce the scenario by creating a single introduce action that can be performed by both actors.

Feature: Two actors meet at a conference and exchange information.

  Background:
    Given a chain is created by "Joe"
    Given "Joe" creates the "main" process using the "condition" scenario
    And   "Joe" is the "initiator" actor of the "main" process
    And   "Jane" is the "recipient" actor of the "main" process

  Scenario:
    When "Joe" runs the "introduce" action of the "main" process with:
      | name         | Joe Smith     |
      | organization | LTO Network   |
    Then the "initiator" actor of the "main" process has:
      | name         | Joe Smith     |
      | organization | LTO Network   |
    And the "main" process is in the "wait_on_recipient" state

    When "Jane" runs the "introduce" action of the "main" process with:
      | name         | Jane Wong     |
      | organization | Acme Inc      |
    Then the "recipient" actor of the "main" process has:
      | name         | Jane Wong     |
      | organization | Acme Inc      |
    And the "main" process is completed

Multiple actors for an action

Allowing 2 actors to perform an action can be done by replacing the actor with a list of actors.

The update instruction needs to update the actor that performed the action. This actor can be selected using current.actor.

actions:
  introduce:
    actors:
      - initiator
      - recipient
    update: current.actor

states:
  initial:
    action: introduce
    transition: wait_on_recipient
  wait_on_recipient:
    action: introduce
    transition: :success

This doesn't quite cut it though, because the initiator could now introduce himself twice to complete the process. That's not what we want.

Conditional action

We'll add a second Scenario section to make sure a second introduction is ignored.

Feature: Two actors meet at a conference and exchange information.

  Background:
    Given a chain is created by "Joe"
    Given "Joe" creates the "main" process using the "handshake" scenario
    And   "Joe" is the "initiator" actor of the "main" process
    And   "Jane" is the "recipient" actor of the "main" process

  Scenario:
    ...

  Scenario:
    When "Joe" runs the "introduce" action of the "main" process with:
      | name         | Joe Smith     |
      | organization | LTO Network   |
    Then the "initiator" actor of the "main" process has:
      | name         | Joe Smith     |
      | organization | LTO Network   |
    And the "main" process is in the "wait_on_recipient" state

    When "Joe" runs the "introduce" action of the "main" process with:
      | name         | Joe Smith     |
      | organization | LTO Network   |
    Then the "main" process is in the "wait_on_recipient" state

Running this test will fail, because the process is completed and not in the wait on recipient state.

We'll change the scenario, so an actor can only introduce himself/herself if the actor's name is unknown.

We can add a condition to an action. The condition is always interpreted as boolean. With data instructions like <eval>, the condition can be determined based on process data.

actions:
  introduce:
    actors:
      - initiator
      - recipient
    condition: !eval current.actor.name == null
    update: current.actor

states:
  initial:
    action: introduce
    transition: wait_on_recipient
  wait_on_recipient:
    action: introduce
    transition: :success

Data instructions can only use data that's available in the process, as they must be deterministic for all parties to have the same result.

current.actor is not available for the properties like the title (action, response and state) and state instructions. The actor isn't determined yet when these are calculated.

Transition validation

An ill intended Joe could still get around this by not specifying his name when he introduces himself. Since the name isn't set he can perform introduce while in the wait on recipient state.

We don't want the process to fail if Joe doesn't give his name. Instead the process will stay in the initial state until he does.

Feature: Two actors meet at a conference and exchange information.

  Background:
    Given a chain is created by "Joe"
    Given "Joe" creates the "main" process using the "handshake" scenario
    And   "Joe" is the "initiator" actor of the "main" process
    And   "Jane" is the "recipient" actor of the "main" process

  Scenario:
    ...

  Scenario:
    ...

  Scenario:
    When "Joe" runs the "introduce" action of the "main" process with:
      | organization | LTO Network   |
    Then the "initiator" actor of the "main" process has:
      | name         |               |
      | organization | LTO Network   |
    And the "main" process is in the "initial" state

    When "Joe" runs the "introduce" action of the "main" process with:
      | nonsense     | chatter       |
    Then the "main" process is in the "initial" state

    When "Joe" runs the "introduce" action of the "main" process
    Then the "main" process is in the "initial" state

    When "Joe" runs the "introduce" action of the "main" process with:
      | name         | Joe Smith     |
    Then the "initiator" actor of the "main" process has:
      | name         | Joe Smith     |
      | organization | LTO Network   |
    And the "main" process is in the "wait_on_recipient" state

To achieve this, add a condition to the transition in the initial state. Transition conditions are evaluated after the action is performed and updated instructions are processed.

Jane is also be required to give her name.

actions:
  introduce:
    actors:
      - initiator
      - recipient
    condition: !eval current.actor.name == null
    update: current.actor

states:
  initial:
    action: introduce
    transitions:
      - transition: wait_on_recipient
        condition: !eval actors.initiator.name != null
  wait_on_recipient:
    action: introduce
    transitions:
      - transition: :success
        condition: !eval actors.recipient.name != null

Any order will do

According to the scenario, Joe must first give his name and then Jane should give her name. But the order isn't really important. Both actors should introduce themselves with their name. When that's done, the process is complete.

Feature: Two actors meet at a conference and exchange information.

  Background:
    Given a chain is created by "Joe"
    Given "Joe" creates the "main" process using the "handshake" scenario
    And   "Joe" is the "initiator" actor of the "main" process
    And   "Jane" is the "recipient" actor of the "main" process

  Scenario:
    When "Jane" runs the "introduce" action of the "main" process with:
      | name         | Jane Wong     |
      | organization | Acme Inc      |
    Then the "recipient" actor of the "main" process has:
      | name         | Jane Wong     |
      | organization | Acme Inc      |
    And the "main" process is in the "initial" state

    When "Joe" runs the "introduce" action of the "main" process with:
      | name         | Joe Smith     |
      | organization | LTO Network   |
    Then the "initiator" actor of the "main" process has:
      | name         | Joe Smith     |
      | organization | LTO Network   |
    And the "main" process is completed

The introduce action can already be performed by both actors in the initial state and only by actor of whom the name is not know yet. We only need to combine the two states.

$schema: "https://specs.letsflow.io/v0.2.0/scenario/schema.json#"
title: Under one condition

actors:
  initiator:
    $schema: "http://json-schema.org/schema#"
    title: Initiator
    type: object
    properties:
      name:
        type: string
      organization:
        type: string
  recipient:
    $schema: "http://json-schema.org/schema#"
    title: Recipient
    type: object
    properties:
      name:
        type: string
      organization:
        type: string

actions:
  introduce:
    actors:
      - initiator
      - recipient
    condition: !eval current.actor.name == null
    update: current.actor

states:
  initial:
    action: introduce
    transitions:
      - transition: :success
        condition: !eval actors.initiator.name != null && actors.recipient.name != null

Now you!

In the "A proper introduction" tutorial, you modified the scenario adding steps for asking the e-mail address from the recipient.

In that scenario, we only want to only consider the process to successfully completed if the recipient has given her e-mail address. If the init_iator ends the conversation or if the _recipient doesn't give her e-mail address, the process is cancelled.

Rather than making an separate action or response for the recipient to skip not give the e-mail address, use a condition to check to transition to ":success" or ":cancelled".

  1. Copy the introduction scenario and test file you modified as the previous assignment.

  2. Edit main.feature, specifying that the process is cancelled if it's completed by the initiator.

  3. Add a third Scenario section, copying the second. Change the last action so the reply of recipient on the request doesn't have any data. In this case the process should also be cancelled.

  4. Modify the scenario so the new test succeeds.

Last updated