Ctrl K
ComunidadGuías…

tests/conftest.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Shared pytest fixtures for jobd tests."""

import time
from datetime import UTC, datetime

import pytest
from fastapi.testclient import TestClient

from jobd.app import build_app


@pytest.fixture(autouse=True)
def _bypass_auth_for_tests(monkeypatch):
    """Existing broker tests assume an open broker. Tests exercising the
    auth layer override these via their own monkeypatch."""
    monkeypatch.setenv("JOBD_ALLOW_NO_AUTH", "1")
    monkeypatch.setenv("JOBD_DISABLE_TAILNET_ACL", "1")


@pytest.fixture(autouse=True)
def _test_scope_namespace():
    """Name every scope a test creates in a namespace no real broker has.

    The worker's startup sweep kills its own namespace, and the namespace is
    derived from the broker URL (JOBD_URL when exported). Tests that launch
    real systemd scopes must not land in a production worker's namespace.
    """
    import os

    import jobd.worker.job_worker as job_worker

    before = job_worker._scope_ns
    job_worker.set_scope_namespace(f"http://jobd-test-suite.invalid:{os.getpid()}")
    yield
    job_worker._scope_ns = before


@pytest.fixture(autouse=True)
def _steady_state_broker():
    """Present the sweeper with a broker that has been up and observing.

    `sweep_once` refuses to run its time-based terminal phases when it cannot
    show that it watched the interval it would reason about — a fresh start, or
    a suspend/stall gap (audit 2026-07-25 H-1). Every sweeper test builds an app
    and sweeps in the same millisecond, which is precisely the "just started"
    shape the guard exists to catch, so without this they would all be
    asserting against a suppressed sweep.

    Declaring steady state here keeps the guard honest in production while
    letting the other tests mean what they always meant. Tests ABOUT the guard
    (tests/unit/test_observation_gap_guard.py) reset this themselves; their own
    autouse fixture runs after this one.
    """
    from jobd.broker import sweeper

    sweeper.reset_observation_state()
    sweeper._process_start_monotonic = time.monotonic() - 86_400
    sweeper._last_sweep_wall = datetime.now(UTC).replace(tzinfo=None)
    yield
    sweeper.reset_observation_state()


@pytest.fixture
def tmp_db_url(tmp_path):
    """Give each test an isolated SQLite file."""
    return f"sqlite:///{tmp_path}/jobd.db"


# The standard one-broker test rig. This exact fixture body was pasted into
# 24 test files (audit 2026-07-05 L3); a file that needs a variant (other
# projects.yaml, docs on, …) defines its own local override as before.
@pytest.fixture
def app(tmp_path, sample_projects_yaml, sample_profiles_yaml, sample_classifier_yaml):
    return build_app(
        db_url=f"sqlite:///{tmp_path}/jobd.db",
        projects_path=sample_projects_yaml,
        profiles_path=sample_profiles_yaml,
        classifier_path=sample_classifier_yaml,
        logs_path=tmp_path / "logs",
    )


@pytest.fixture
def client(app):
    return TestClient(app)


@pytest.fixture
def client_logs(client, tmp_path):
    """(client, logs_dir) pair for tests that inspect events.jsonl / job logs."""
    return client, tmp_path / "logs"


@pytest.fixture
def sample_profiles_yaml(tmp_path):
    path = tmp_path / "profiles.yaml"
    path.write_text("""profiles:
  small:
    vram_gb: 0
    ram_gb: 2
    cpus: 2
    expected_runtime: 10m
    preemptible: true
    host_hint: any
    fast_path: true
  gpu-heavy:
    vram_gb: 28
    ram_gb: 22
    cpus: 8
    expected_runtime: 8h
    preemptible: true
    host_hint: desktop
""")
    return path


@pytest.fixture
def sample_projects_yaml(tmp_path):
    """Default fixture: a few projects, one with a non-trivial defaults block.

    The ``project-a`` defaults block is intentionally empty so existing
    tests (which submit with ``project=project-a`` and assert global
    behavior) continue to pass. Tests that exercise the new defaults
    resolution should use their own narrower fixtures.
    """
    path = tmp_path / "projects.yaml"
    path.write_text("""projects:
  project-b: { priority: 80 }
  project-a: { priority: 55 }
  _default: { priority: 40 }
""")
    return path


@pytest.fixture
def sample_classifier_yaml(tmp_path):
    path = tmp_path / "classifier.yaml"
    path.write_text("""rules:
  - id: sdxl-lora-train
    match:
      - command_regex: "^(bash\\\\s+)?train_lora_v\\\\d+\\\\.sh\\\\b"
    suggest_profile: gpu-heavy
    confidence: high
""")
    return path