Development

Clone the repository

  git clone git@gitlab.com:ska-telescope/src/src-ms/ska-src-ms-system-testing-suite.git

Install dependencies

  poetry install

Run the application

The application can be run locally using Docker Compose, which will set up the necessary environment for testing.

  • Configure environment:

    • cp .env.template .env

    • Edit .env as needed

  • Run tests:

    • docker-compose up --build

Adding new tests

This repository uses pytest-bdd to describe end-to-end flows in Gherkin feature files and implement them with Python step definitions. Below is a concise guide to add a new scenario like the Data Replication Flow example you provided.

Folder Structure

  • Place feature files under: tests/feature/

    • You can group by domain, e.g., tests/feature/replication/

  • Name feature files with .feature suffix, e.g., data_replication_flow.feature

  • Implement step definitions in Python files under tests/, typically named test__steps.py

  • Ensure your step file calls scenarios(…) to auto-load feature files

Example step file discovery snippet:

# tests/test_replication_flow_steps.py
import glob
import os
from pytest_bdd import scenarios

feature_dir = os.path.join(os.path.dirname(__file__), "feature")
feature_files = glob.glob(os.path.join(feature_dir, "*.feature"))
for feature_file in feature_files:
    scenarios(feature_file)

Writing Feature Files

Create a .feature file describing your business flow in Gherkin.

# tests/feature/data_replication_flow.feature
Feature: Data Replication Flow

  Scenario: Replicate file to another RSE and verify its status
    Given a data file exists at a known RSE location
    When user locate the file using the namespace and file name
    Then the system should return the current storage location of the file

    When user replicate the file to another RSE with a specified lifetime
    Then the replication job should be accepted and return a job ID

    When user poll the status of the replication job using the job ID
    Then the system should confirm the job is in progress or completed

Tips:

  • Use clear, imperative language in Given/When/Then.

  • Prefer deterministic steps; avoid environment-specific assumptions unless they are provided via configuration or Examples tables.

  • For multiple inputs, consider Scenario Outline with Examples:

Scenario Outline: Replicate file between RSEs
  Given a data file <file_name> exists at <SOURCE_RSE> location for destination RSE <RSE_NAME> with id <RSE_ID> and parent node <PARENT_NODE_NAME>
  When user locate the file using the namespace and file name
  Then the system should return the current storage location of the file
  When user replicate the file to <RSE_NAME> , <RSE_ID> , Node <PARENT_NODE_NAME> with a specified lifetime
  Then the replication job should be accepted and return a job ID
  When user poll the status of the replication job using the job ID
  Then the system should confirm the job is in progress or completed

  Examples:
    | file_name | SOURCE_RSE | RSE_NAME | RSE_ID                              | PARENT_NODE_NAME |
    | sample1   | SRC_A      | DST_A    | 00000000-0000-0000-0000-000000000001 | node-01          |

Note:
You can generate specific feature files from feature template by creating a script that populates the Examples table with the data you need. Automate the data preparation first—have the script produce all required example rows—then use that output to programmatically write the corresponding .feature files, ensuring each file includes the correctly filled Examples section.

Implementing Step Definitions

Create or extend a step file that binds Gherkin sentences to Python functions.

  • Use pytest-bdd decorators: @given, @when, @then

  • Parse dynamic values with parsers.cfparse and named placeholders matching your scenario

  • Share state across steps with a context fixture (e.g., a SimpleNamespace or namedtuple)

Example step definitions:

# Implement step definitions
@given(parsers.cfparse("Given a data file <file_name> exists at <SOURCE_RSE> location for destination RSE <RSE_NAME> with id <RSE_ID> and parent node <PARENT_NODE_NAME>"))
def file_exists(api_request_context_factory, iam, context, request, file_name, SOURCE_RSE, RSE_NAME, RSE_ID, PARENT_NODE_NAME):
    # Implementation similar to existing step definition
    pass

# Add other required step definitions...

Notes:

  • The placeholders in parsers.cfparse must match those in your .feature steps.

  • Use your project’s existing fixtures/utilities for API clients, configuration, and assertions.

Creating the context fixture

The context fixture allows you to share state between steps in your BDD scenarios. It creates a namespace for storing and accessing data across multiple test steps.

@pytest.fixture
def context():
    """Create test context"""
    return namedtuple("TestContext", ["api_ctx", "file_name", "job_id", "responses"])

Using the context fixture

Setting values in the context
@given("a data file exists at a known RSE location")
def file_exists(api_request_context_factory, iam, context):
    # Create API context with authentication
    exchange_token = iam.exchange_token(fetch_user_token, "data-management-api").json()
    context.api_ctx = api_request_context_factory(service_endpoint, exchange_token["access_token"])
    
    # Set file information
    context.file_name = "test_file.dat"
Accessing values in the context
@when("user locate the file using the namespace and file name")
def locate_file(context):
    """Locate file using API"""
    # Access previously stored values
    api_ctx = context.api_ctx
    file_name = context.file_name
    
    # Store new response in context
    context.located_response = api_ctx.get(f"data/locate/{namespace}/{file_name}")

Notes:

  • Define all expected attributes in the namedtuple

  • Use descriptive attribute names

  • Set values as early as possible in the test flow

  • Use context only for data that needs to be shared between steps

Reusing the fixture

  • Common fixtures (API clients, configuration, IAM helpers, custom assertions) should be provided by tests/conftest.py or shared utility modules under tests/resources/.

  • Prefer project-provided assertion helpers for consistent reporting.

  • Store transient state in the context fixture to pass data between steps.

Scenario Outlines and data-driven tests

  • Use Scenario Outline with an Examples table when the same flow must be validated across multiple inputs.

  • Keep the number of examples reasonable to keep runs fast.

  • If inputs are environment-specific, drive them from configuration or CI variables instead of hardcoding.

Best Practices

  • Keep feature files focused on business scenarios

  • Use parsers.cfparse for parameterized steps

  • Handle API responses consistently with helper functions

  • Include appropriate assertions in “Then” steps

  • Use descriptive scenario and step names

  • Add proper error handling and timeouts for polling operations