Files
sientia-dataops-orchestrato…/e2e/scenarios.md
vitor-aignosi 11e350a6ce SIENTIAPDE-1974
Add e2e tag-database scenarios and cover worker/email/formatter gaps

- Add e2e scenarios 1.9.1-1.9.4 for PI Web API through the tag
  database: scouter and output tag resolution, mixed-connector schedule
  hold, and OPC slot unchanged by PI connectors. Seed connectors and
  connector_tags from a new scenario input.
- Tighten the happy-path e2e to assert both schedules and that no PI
  output config is produced when the tag database is unused.
- Drop dead `if self.server` / `if notification_handler` guards in the
  email reconnect and worker shutdown paths, since both are always set.
- Log the traceback inside the message on worker failure, as the
  sientia_do Logger.error does not accept exc_info.
- Add unit tests for the worker failure exit, Prometheus startup
  failure, email reconnect without a password, and formatter, mongo,
  temporal manager and orchestrator edge cases.
2026-10-28 16:07:10 -03:00

7.0 KiB

E2E Scenario Documentation — Orchestrator

Functional reference for orchestrator E2E scenarios. Tests live under e2e/, use @pytest.mark.e2e, and run with:

pytest e2e/ --override-ini testpaths=e2e -m e2e

Every section below maps 1:1 to an actual test function (named in each section, or as a bullet under a Source: heading) — this file describes what the suite verifies today, not planned or aspirational coverage. Adding a scenario here without a matching test (or vice versa) is a doc bug; fix the mismatch instead of leaving it for later.

Execution context

  • MongoDB, Redis, PostgreSQL: testcontainers (session-scoped).
  • SMTP: in-process aiosmtpd (e2e/smtp_test_server.py).
  • Temporal: WorkflowEnvironment.start_local() with stub workers on scouter / laborious.
  • Production code under orchestrator/** is not mocked; only Logger may be a MagicMock.

1. Orchestrator workflow

Source: e2e/test_orchestrator_main_workflow.py

1.1.1 Happy path

Pipelines in Mongo → both Temporal schedules created in the laborious namespace, one orchestrated_schedules entry per pipeline, and no PI Web API output config when no pipeline uses the tag database.

1.3.1 Create-only

New pipeline only → Temporal schedule created for it.

1.3.3 Delete-only

Pipeline removed from Mongo → previously-created Temporal schedule deleted.

1.5.1 Empty pipelines

No active pipelines → no orchestrated_schedules documents written, even with a pre-existing unrelated Temporal schedule present.

1.6.1 TTL index bootstrap

First run creates TTL index on notification collection used by scouter pipelines.

1.7.1 Simple metrics parallel windows

Two simple_metrics pipelines for the same model_id (60 min and 1440 min windows) → one Temporal schedule per window, each with its own frequency as interval.

1.7.2 Adaptive frequency escalation/recovery round trip

Laborious-style write (adaptive_state.escalated=true + BSON-datetime updated_at) on one window → next run switches only that schedule to frequency_alert; the recovery write switches it back to frequency.

1.8.1 Drift window and chunk_period

Two drift pipelines on frequency='1m': one without interval_minutes and with an invalid chunk_period='hour' → workflow input interval=1 (frequency, no overlap), chunk_period='min' and exactly one DRIFT_CHUNK_PERIOD_INVALID notification; one with interval_minutes=60 and chunk_period='s' → both forwarded unchanged, no warning (SIENTIAPDE-2212).

1.9 PI Web API through the tag database (connectors-pi-web-api)

Scenario input: orchestrator_pi_web_api.json (two pi_web_api connectors, one opc_ua connector, their connector_tags, PI Web API pipelines by tag_id and legacy ones, and an OPC scouter by tag_id).

1.9.1 PI scouter through the tag database

pi_web_api_scouter with tag_id read tags → model_tags WebID from connector_tags.address, pi_web_api_query with the connector period under the pipeline max_count (api_timeout capped at the frequency), and connection with base URL and the secret reference only. An unknown tag_id is left out and notified (ORCHESTRATOR_PI_TAG_NOT_FOUND_DURING_SCHEDULE_CONFIGURATION). A legacy pipeline (inline webid) gets the pre-change input, without connection.

1.9.2 PI output tags through the tag database

predictions_batch pi_web_api_output_config given as tag_id lists → {tag_name: WebID} from Write / Prediction Confidence tags; a Read tag given as a confidence tag is left out and notified (ORCHESTRATOR_PI_TAG_WRONG_CATEGORY_DURING_SCHEDULE_CONFIGURATION). A legacy {tag_name: webid} mapping passes through unchanged.

1.9.3 Mixed PI connectors hold the schedule

A pipeline updated to read tags of two PI connectors → its existing scouter schedule is neither updated nor deleted, and the conflict is notified (ORCHESTRATOR_PI_MIXED_CONNECTORS_DURING_SCHEDULE_CONFIGURATION).

1.9.4 OPC slot unchanged by PI connectors

The OPC slot v2 written to Redis for an OPC scouter by tag_id is the expected one with the PI connectors and tags present, and identical after removing them.


2. Alerts workflow

Source: e2e/test_alerts_main_workflow.py

A.1.1 Happy path

ERROR notification → one SMTP message, one log_report row, Redis cache key.

A.1.2 TTL duplicate suppression

Second run with same data and cache seeded → no extra email or log row.

A.2.1 Group filtering

Receiver group levels / ignore_models_id honored, both halves now covered: the query-time union/intersection across active groups (SIENTIAPDE-2011, see LoadNotificationPackage below) and the per-group precise slice (SIENTIAPDE-2012) — a group only receives notifications matching its own levels/ignore_models_id, even when the query-time filter widened the fetch to satisfy another active group's broader configuration.

A.2.2 Model_id allow-list

Receiver group model_ids (SIENTIAPDE-2149) honored: a group configuring it only receives notifications from those models, even when another active group with no such restriction widens the query-time fetch to include every model.

A.3.1 Empty queue

No notifications → no SMTP, no Postgres row.


3. Reports workflow

Source: e2e/test_reports_main_workflow.py

R.1.1 Happy path

Mixed ERROR/WARNING/INFO → one HTML email with all section headings.

R.1.2 Per-level rendering

Single-level notifications → only matching section in HTML body.

R.2.1 Empty queue

No notifications → no SMTP, no Postgres row.


4. Subworkflows

LoadNotificationPackage — e2e/test_subworkflow_load_notification_package.py

  • No prior Redis timestamp → all matching notifications returned, max timestamp stored.
  • Prior timestamp → only newer notifications returned.
  • Query-time filter (SIENTIAPDE-2011) → union of active groups' levels, intersection of their ignore_models_id; a model ignored by only one of several active groups is not excluded from the fetch.
  • Model_id allow-list union at query time (SIENTIAPDE-2149) → when every active group sets model_ids, the fetch is restricted to the union of every group's allow-list; a model outside that union is never fetched at all, even for a group that would otherwise want it via a different axis.
  • Empty Mongo → no Redis timestamp write.
  • Batch size cap (SIENTIAPDE-2013) → with 5 pending notifications and a batch size of 2, each run returns exactly the next-oldest 2 (sorted ascending by timestamp), and the Redis cursor advances only to what was actually returned, so three successive runs drain the backlog in order instead of skipping anything.
  • Batch size 0 means unlimited (SIENTIAPDE-2013) → the configured default (0) does not cap the query; 12 pending notifications all come back in one run, verified against a real MongoDB instance rather than assumed from pymongo's documented convention.

ProcessNotifications — e2e/test_subworkflow_process_notifications.py

  • Full round trip: HTML → SMTP → log_report in Postgres.
  • Empty receiver groups → {}.