chore: import upstream snapshot with attribution
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
TypeScript SDK Library Integration Tests / Check Secrets (push) Has been cancelled
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Has been cancelled
SDK Library Integration Tests Runner / Check Secrets (push) Has been cancelled
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Has been cancelled
SDK Library Integration Tests Runner / Build (push) Has been cancelled
SDK Library Integration Tests Runner / openai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / llama_index_tests (push) Has been cancelled
SDK Library Integration Tests Runner / anthropic_tests (push) Has been cancelled
SDK Library Integration Tests Runner / mistral_tests (push) Has been cancelled
SDK Library Integration Tests Runner / groq_tests (push) Has been cancelled
SDK Library Integration Tests Runner / aisuite_tests (push) Has been cancelled
SDK Library Integration Tests Runner / haystack_tests (push) Has been cancelled
SDK Library Integration Tests Runner / dspy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v1_tests (push) Has been cancelled
SDK Library Integration Tests Runner / genai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Has been cancelled
SDK Library Integration Tests Runner / bedrock_tests (push) Has been cancelled
SDK Library Integration Tests Runner / litellm_tests (push) Has been cancelled
SDK Library Integration Tests Runner / harbor_tests (push) Has been cancelled
SDK Library Integration Tests Runner / Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / render-equality (push) Has been cancelled
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Has been cancelled
Python BE E2E Tests / Python BE E2E (push) Has been cancelled
Python Backend Tests / run-python-backend-tests (push) Has been cancelled
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
SDK E2E Libraries Integration Tests / Check Secrets (push) Has been cancelled
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Has been cancelled
SDK E2E Libraries Integration Tests / build-opik (push) Has been cancelled
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Has been cancelled
TypeScript SDK Build & Publish / build-and-publish (push) Has been cancelled
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Has been cancelled
Backend Tests / discover-tests (push) Has been cancelled
Backend Tests / ${{ matrix.name }} (push) Has been cancelled
Build and Publish SDK / build-and-publish (push) Has been cancelled
Build Opik Docker Images / set-version (push) Has been cancelled
Build Opik Docker Images / build-backend (push) Has been cancelled
Build Opik Docker Images / build-sandbox-executor-python (push) Has been cancelled
Build Opik Docker Images / build-python-backend (push) Has been cancelled
Build Opik Docker Images / build-frontend (push) Has been cancelled
Build Opik Docker Images / create-git-tag (push) Has been cancelled
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Has been cancelled
Docs - Publish / run (push) Has been cancelled
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Has been cancelled
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Has been cancelled
Frontend Unit Tests / Test on Node 20 (push) Has been cancelled
Guardrails E2E Tests / Select Python version matrix (push) Has been cancelled
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Guardrails E2E Tests / 📢 Slack Notification (push) Has been cancelled
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Has been cancelled
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Has been cancelled
Lint Opik Helm Chart / unittest-helm-chart (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:44 +08:00
commit 5a558eb09e
11579 changed files with 1795921 additions and 0 deletions
+285
View File
@@ -0,0 +1,285 @@
import base64
import dataclasses
import hashlib
import hmac as hmac_mod
import os
import re
import shutil
import subprocess
import sys
import threading
import time
import uuid
import pytest
import opik
import opik.api_objects.opik_client
from opik.api_objects import rest_helpers
from opik.cli.local_runner.pairing import hkdf_sha256
from opik.rest_api import core as rest_api_core
ECHO_APP = os.path.join(os.path.dirname(__file__), "echo_app.py")
OPIK_CLI = shutil.which("opik") or "opik"
RUNNER_STARTUP_TIMEOUT = 30
_phase_report_key = pytest.StashKey[dict]()
@dataclasses.dataclass
class RunnerInfo:
runner_id: str
process: subprocess.Popen
output_lines: list
bridge_key: bytes = b""
@dataclasses.dataclass
class TestProject:
id: str
name: str
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
item.stash.setdefault(_phase_report_key, {})[rep.when] = rep
@pytest.fixture()
def opik_client(configure_e2e_tests_env, shutdown_cached_client_after_test):
client = opik.api_objects.opik_client.Opik(batching=True)
yield client
client.end()
@pytest.fixture()
def api_client(opik_client):
return opik_client.rest_client
@pytest.fixture()
def subprocess_env(opik_client):
cfg = opik_client.config
env = os.environ.copy()
env["OPIK_URL_OVERRIDE"] = cfg.url_override
if cfg.api_key:
env["OPIK_API_KEY"] = cfg.api_key
if cfg.workspace:
env["OPIK_WORKSPACE"] = cfg.workspace
return env
@pytest.fixture()
def project(api_client, temporary_project_name) -> TestProject:
"""Per-test project, created on setup. The shared ``temporary_project_name``
fixture handles name generation and teardown."""
try:
api_client.projects.create_project(name=temporary_project_name)
except rest_api_core.ApiError:
pass
project_id = rest_helpers.resolve_project_id_by_name(
api_client, temporary_project_name
)
return TestProject(id=project_id, name=temporary_project_name)
def _drain_stdout(proc, output_lines):
for line in proc.stdout:
output_lines.append(line.rstrip())
def _parse_pairing_url(output_lines, timeout):
"""Wait for the CLI to print the pairing URL, then parse the fragment.
The URL may be split across multiple stdout lines by the terminal.
Join all output and search for the full URL.
"""
deadline = time.monotonic() + timeout
url_pattern = re.compile(
r"(https?://\S+/opik/pair/v1(?:\?[^#\s]*)?#[A-Za-z0-9_\-]+)"
)
while time.monotonic() < deadline:
joined = "".join(list(output_lines))
match = url_pattern.search(joined)
if match:
url = match.group(1)
fragment = url.split("#", 1)[1]
padded = fragment + "=" * ((4 - len(fragment) % 4) % 4)
raw = base64.urlsafe_b64decode(padded)
if len(raw) < 65:
time.sleep(0.05)
continue
name_len = raw[64]
if len(raw) < 65 + name_len:
time.sleep(0.05)
continue
return {
"session_id": str(uuid.UUID(bytes=raw[0:16])),
"activation_key": raw[16:48],
"project_id": str(uuid.UUID(bytes=raw[48:64])),
"runner_name": raw[65 : 65 + name_len].decode("utf-8"),
}
time.sleep(0.05)
return None
def _activate_session(api_client, activation_key, session_id, runner_name):
session_id_bytes = uuid.UUID(session_id).bytes
runner_name_hash = hashlib.sha256(runner_name.encode("utf-8")).digest()
message = session_id_bytes + runner_name_hash
sig = hmac_mod.new(activation_key, message, hashlib.sha256).digest()
hmac_b64 = base64.b64encode(sig).decode("ascii")
api_client.pairing.activate_pairing_session(
session_id, runner_name=runner_name, hmac=hmac_b64
)
def _start_runner(
api_client, subprocess_env, project_id, opik_client, request, cli_args
):
proc = subprocess.Popen(
cli_args,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
env=subprocess_env,
)
output_lines = []
drain_thread = threading.Thread(
target=_drain_stdout, args=(proc, output_lines), daemon=True
)
drain_thread.start()
# Wait for the CLI to print its pairing URL, then parse the fragment
payload = _parse_pairing_url(output_lines, timeout=15)
if payload is None:
proc.terminate()
proc.wait(timeout=5)
drain_thread.join(timeout=5)
pytest.fail(
"CLI did not print pairing URL within 15s.\n"
"Output:\n" + "\n".join(output_lines)
)
# Activate the CLI's session (simulates browser clicking Connect)
_activate_session(
api_client,
payload["activation_key"],
payload["session_id"],
payload["runner_name"],
)
# Wait for the CLI to detect connected status
deadline = time.monotonic() + RUNNER_STARTUP_TIMEOUT
connected = False
while time.monotonic() < deadline:
for line in list(output_lines):
if "Paired" in line:
connected = True
break
if connected:
break
if proc.poll() is not None:
break
time.sleep(0.1)
if not connected:
proc.terminate()
proc.wait(timeout=5)
drain_thread.join(timeout=5)
pytest.fail(
f"Runner did not connect within {RUNNER_STARTUP_TIMEOUT}s.\n"
f"Output:\n" + "\n".join(output_lines)
)
# Get runner_id by polling the API
runners_page = api_client.runners.list_runners(project_id=project_id, size=10)
runner_id = None
if runners_page.content:
for r in runners_page.content:
if r.status == "connected":
runner_id = r.id
break
if runner_id is None:
proc.terminate()
proc.wait(timeout=5)
drain_thread.join(timeout=5)
pytest.fail("No connected runner found after activation")
bridge_key = hkdf_sha256(
ikm=payload["activation_key"],
salt=uuid.UUID(payload["session_id"]).bytes,
info=b"opik-bridge-v1",
)
info = RunnerInfo(
runner_id=runner_id,
process=proc,
output_lines=output_lines,
bridge_key=bridge_key,
)
yield info
# Runner CLI + echo_app don't need a graceful window in tests; SIGTERM →
# short wait → SIGKILL is fine. Drops per-test teardown from ~10s to ~2s.
proc.terminate()
try:
proc.wait(timeout=2)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait()
drain_thread.join(timeout=2)
report = request.node.stash.get(_phase_report_key, {})
if report.get("call") and report["call"].failed:
print(f"\n--- Runner output (runner_id={runner_id}) ---")
print("\n".join(output_lines))
print("--- End runner output ---")
@pytest.fixture()
def runner_process(api_client, subprocess_env, project, opik_client, request):
"""Endpoint runner — for job/agent E2E tests."""
yield from _start_runner(
api_client,
subprocess_env,
project.id,
opik_client,
request,
cli_args=[
OPIK_CLI,
"endpoint",
"--project",
project.name,
"--",
sys.executable,
ECHO_APP,
],
)
@pytest.fixture()
def bridge_runner_process(api_client, subprocess_env, project, opik_client, request):
"""Connect runner — for bridge command E2E tests."""
yield from _start_runner(
api_client,
subprocess_env,
project.id,
opik_client,
request,
cli_args=[
OPIK_CLI,
"connect",
"--project",
project.name,
],
)
+36
View File
@@ -0,0 +1,36 @@
"""Agent app used by the runner e2e tests.
Contains two entrypoints:
- echo: basic echo function
- echo_config: echo with a configurable greeting (used in mask tests)
"""
import threading
import opik
@opik.track(entrypoint=True)
def echo(message: str) -> str:
print(f"echo stdout: {message}")
return f"echo: {message}"
class EchoConfig(opik.Config):
greeting: str
@opik.track(entrypoint=True)
def echo_config(message: str) -> str:
client = opik.Opik()
cfg = client.get_or_create_config(
fallback=EchoConfig(greeting="fallback-greeting"),
)
return f"{cfg.greeting}: {message}"
if __name__ == "__main__":
# Analogous to uvicorn.run() in a FastAPI app: @opik.track(entrypoint=True) registers
# handlers at import time, and this blocks the main thread so the runner loop stays
# alive to process jobs.
threading.Event().wait()
@@ -0,0 +1,195 @@
"""Happy-path e2e tests for the bridge command system.
Exec tests: echo, nonzero exit, background process.
File operation flow: write → list → search → edit → read, all via the API.
"""
import time
from opik.rest_api.errors.conflict_error import ConflictError
from opik.runner.bridge_auth import sign
from .conftest import RunnerInfo
_BRIDGE_READY_TIMEOUT = 15
def _submit_and_wait(api_client, runner_id, cmd_type, args, bridge_key=b""):
if bridge_key:
args = dict(args)
args["_hmac"] = sign(bridge_key, cmd_type, args)
deadline = time.monotonic() + _BRIDGE_READY_TIMEOUT
while True:
try:
resp = api_client.runners.create_bridge_command(
runner_id,
type=cmd_type,
args=args,
)
break
except ConflictError:
if time.monotonic() > deadline:
raise
time.sleep(1)
return api_client.runners.get_bridge_command(
runner_id,
resp.command_id,
wait=True,
timeout=15,
)
# ---------------------------------------------------------------------------
# Auth tests
# ---------------------------------------------------------------------------
def test_bridge_command__unsigned__fails_with_auth_failed(
api_client, bridge_runner_process: RunnerInfo
):
cmd = _submit_and_wait(
api_client,
bridge_runner_process.runner_id,
"Exec",
{"command": "echo should-not-run"},
)
assert cmd.status == "failed"
assert cmd.error["code"] == "auth_failed"
def test_bridge_command__invalid_hmac__fails_with_auth_failed(
api_client, bridge_runner_process: RunnerInfo
):
cmd = _submit_and_wait(
api_client,
bridge_runner_process.runner_id,
"Exec",
{"command": "echo should-not-run", "_hmac": "dGhpcyBpcyBub3QgYSB2YWxpZCBobWFj"},
)
assert cmd.status == "failed"
assert cmd.error["code"] == "auth_failed"
# ---------------------------------------------------------------------------
# Exec tests
# ---------------------------------------------------------------------------
def test_bridge_exec_echo(api_client, bridge_runner_process: RunnerInfo):
"""Submit a simple echo command and verify the result."""
marker = f"bridge-e2e-{int(time.time())}"
cmd = _submit_and_wait(
api_client,
bridge_runner_process.runner_id,
"Exec",
{"command": f"echo {marker}"},
bridge_key=bridge_runner_process.bridge_key,
)
assert cmd.status == "completed"
assert marker in cmd.result["stdout"]
assert cmd.result["exit_code"] == 0
def test_bridge_exec_nonzero_exit(api_client, bridge_runner_process: RunnerInfo):
"""Verify non-zero exit codes are returned correctly."""
cmd = _submit_and_wait(
api_client,
bridge_runner_process.runner_id,
"Exec",
{"command": "exit 42"},
bridge_key=bridge_runner_process.bridge_key,
)
assert cmd.status == "completed"
assert cmd.result["exit_code"] == 42
def test_bridge_exec_background(api_client, bridge_runner_process: RunnerInfo):
"""Submit a background command, verify PID is returned."""
cmd = _submit_and_wait(
api_client,
bridge_runner_process.runner_id,
"Exec",
{"command": "sleep 30", "background": True},
bridge_key=bridge_runner_process.bridge_key,
)
assert cmd.status == "completed"
assert "pid" in cmd.result
assert cmd.result["status"] == "running"
# ---------------------------------------------------------------------------
# File operation flow: write → list → search → edit → read
# ---------------------------------------------------------------------------
def test_bridge_file_operations(api_client, bridge_runner_process: RunnerInfo):
"""Write a file, find it with list/search, edit it, and read back."""
rid = bridge_runner_process.runner_id
bk = bridge_runner_process.bridge_key
marker = f"xyzzy_{int(time.time())}"
filename = f"bridge_e2e_{int(time.time())}.py"
original_content = f"# {marker}\n"
# 1. Write
cmd = _submit_and_wait(
api_client,
rid,
"WriteFile",
{"path": filename, "content": original_content},
bridge_key=bk,
)
assert cmd.status == "completed"
assert cmd.result["created"] is True
# 2. ListFiles — new file should appear in the root
cmd = _submit_and_wait(
api_client,
rid,
"ListFiles",
{"pattern": f"{filename}"},
bridge_key=bk,
)
assert cmd.status == "completed"
assert any(filename in f for f in cmd.result["files"]), (
f"{filename} not in {cmd.result['files']}"
)
# 3. EditFile — replace content
cmd = _submit_and_wait(
api_client,
rid,
"EditFile",
{
"path": filename,
"edits": [{"old_string": marker, "new_string": f"edited_{marker}"}],
},
bridge_key=bk,
)
assert cmd.status == "completed"
assert cmd.result["edits_applied"] == 1
# 4. ReadFile — verify the edit took effect
cmd = _submit_and_wait(
api_client,
rid,
"ReadFile",
{"path": filename},
bridge_key=bk,
)
assert cmd.status == "completed"
assert f"edited_{marker}" in cmd.result["content"]
# 5. Cleanup via Exec
_submit_and_wait(
api_client,
rid,
"Exec",
{"command": f"rm {filename}"},
bridge_key=bk,
)
@@ -0,0 +1,191 @@
"""Happy-path e2e tests for the local runner.
Test 1 (basic): register echo agent, create job, verify trace output.
Test 2 (mask): register echo_config agent, create mask, create job with
mask_id, verify the mask value appears in the trace output.
"""
import time
from typing import Optional
import opik
import opik.rest_api.client as rest_api_client
from opik.api_objects.agent_config.config import ConfigManager
from .conftest import RunnerInfo
JOB_COMPLETION_TIMEOUT = 30
TRACE_PROPAGATION_TIMEOUT = 30
AGENT_REGISTRATION_TIMEOUT = 10
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def submit_job(
api: rest_api_client.OpikApi,
agent_name: str,
message: str,
project_id: str,
mask_id: Optional[str] = None,
) -> None:
"""Create a job for the given agent."""
api.runners.create_job(
agent_name=agent_name,
inputs={"message": message},
project_id=project_id,
mask_id=mask_id,
)
def wait_for_completed_job(
api: rest_api_client.OpikApi, runner_id: str, match_text: str
):
"""Poll list_jobs until a completed job whose inputs contain *match_text* appears."""
result = []
def _find():
page = api.runners.list_jobs(runner_id=runner_id, size=20)
if page.content:
for j in page.content:
if j.status == "completed" and j.inputs and match_text in str(j.inputs):
result.clear()
result.append(j)
return True
return False
assert opik.synchronization.until(
_find,
max_try_seconds=JOB_COMPLETION_TIMEOUT,
allow_errors=True,
), f"No completed job with '{match_text}' found within {JOB_COMPLETION_TIMEOUT}s"
return result[0]
def find_trace_by_input(
api: rest_api_client.OpikApi, project_name: str, match_text: str
):
"""Poll until a trace whose input contains *match_text* appears and has output."""
result = []
def _find():
page = api.traces.get_traces_by_project(
project_name=project_name,
size=20,
)
if page.content:
for t in page.content:
if t.input and match_text in str(t.input) and t.output:
result.clear()
result.append(t)
return True
return False
assert opik.synchronization.until(
_find,
max_try_seconds=TRACE_PROPAGATION_TIMEOUT,
allow_errors=True,
), f"No trace with '{match_text}' found within {TRACE_PROPAGATION_TIMEOUT}s"
return result[0]
def wait_for_agent_registration(
api: rest_api_client.OpikApi, agent_name: str, project_id: str
) -> None:
"""Poll until the agent is registered with any runner in the project."""
def _is_agent_registered():
runners_page = api.runners.list_runners(project_id=project_id, size=50)
if runners_page.content:
for runner in runners_page.content:
if runner.agents:
for agent in runner.agents:
if agent.name == agent_name:
return True
return False
if not opik.synchronization.until(
_is_agent_registered,
max_try_seconds=AGENT_REGISTRATION_TIMEOUT,
allow_errors=True,
):
raise AssertionError(
f"Agent '{agent_name}' was not registered within {AGENT_REGISTRATION_TIMEOUT}s"
)
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
def test_runner_happy_path(api_client, runner_process: RunnerInfo, project):
"""Basic: register echo agent, run job, verify job result, trace output, and job logs."""
message = f"hello-e2e-{int(time.time())}"
wait_for_agent_registration(api_client, "echo", project.id)
submit_job(api_client, "echo", message, project.id)
job = wait_for_completed_job(api_client, runner_process.runner_id, message)
assert job.result is not None, "Completed job should have a result"
assert f"echo: {message}" in str(job.result)
assert job.trace_id is not None, "Completed job should have a trace_id"
trace = find_trace_by_input(api_client, project.name, message)
assert f"echo: {message}" in str(trace.output)
logs_result = []
def _find_logs():
logs = api_client.runners.get_job_logs(job.id)
if logs:
logs_result.clear()
logs_result.extend(logs)
return True
return False
assert opik.synchronization.until(
_find_logs,
max_try_seconds=5,
allow_errors=True,
), f"Expected job logs for job {job.id}, got none"
log_text = " ".join(entry.text for entry in logs_result)
assert message in log_text, f"Expected '{message}' in job logs, got: {log_text}"
def test_runner_with_mask(opik_client, api_client, runner_process: RunnerInfo, project):
"""Mask: register echo_config agent, create mask, verify mask value in job result and trace."""
message = f"mask-e2e-{int(time.time())}"
custom_greeting = f"custom-greeting-{int(time.time())}"
wait_for_agent_registration(api_client, "echo_config", project.id)
manager = ConfigManager(
project_name=project.name,
rest_client_=opik_client.rest_client,
)
manager.create_blueprint(
parameters={"greeting": "default-greeting"},
)
mask_id = manager.create_mask(
parameters={"greeting": custom_greeting},
)
submit_job(api_client, "echo_config", message, project.id, mask_id=mask_id)
job = wait_for_completed_job(api_client, runner_process.runner_id, message)
assert job.result is not None, "Completed job should have a result"
assert custom_greeting in str(job.result)
assert job.trace_id is not None, "Completed job should have a trace_id"
trace = find_trace_by_input(api_client, project.name, message)
assert custom_greeting in str(trace.output), (
f"Expected '{custom_greeting}' in trace output, got: {trace.output}"
)