From 473424227a40da99f0323da3932bd0aa54c41b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E7=AD=96?= Date: Mon, 9 Feb 2026 18:03:27 -0800 Subject: [PATCH] test(ci): add smoke test lane and pytest baseline config Introduce a dedicated smoke test layer under tests/smoke with deterministic checks that validate key runtime construction paths without external model calls. Wire smoke tests into CI as a non-blocking phase-2 signal (continue-on-error) so the team can observe stability before turning it into a required gate. Add pytest.ini marker/testpath baseline and document local smoke commands in CONTRIBUTING for consistent developer and agent workflows. --- .github/workflows/ci-gate.yml | 23 ++++++++++++ CONTRIBUTING.md | 6 +++ pytest.ini | 5 +++ tests/smoke/test_ci_smoke.py | 71 +++++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 pytest.ini create mode 100644 tests/smoke/test_ci_smoke.py diff --git a/.github/workflows/ci-gate.yml b/.github/workflows/ci-gate.yml index 05cf4cc1..1abe83f9 100644 --- a/.github/workflows/ci-gate.yml +++ b/.github/workflows/ci-gate.yml @@ -46,3 +46,26 @@ jobs: - name: Build gate (phase-1 compile check) run: python -m compileall -q dare_framework tests + + smoke-tests: + name: smoke-tests + runs-on: ubuntu-latest + needs: [lint, build] + # Phase-1 rollout: publish smoke signal without blocking merges yet. + continue-on-error: true + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Install smoke-test dependencies + run: | + python -m pip install --upgrade pip + python -m pip install pytest pytest-asyncio langchain-openai langchain-core httpx starlette uvicorn + + - name: Run smoke tests + run: pytest -q tests/smoke -m smoke diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 420e7210..8107aafa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,11 +14,17 @@ This repository uses PR-based delivery with CI gates to keep `main` stable under These checks are designed to be fast and low-friction first. We will tighten them in later phases. +## Smoke Tests (phase-2 rollout) +- CI job: `smoke-tests` +- Current mode: non-blocking (`continue-on-error`), used as signal collection before becoming required. +- Scope: `tests/smoke/` only, deterministic checks with no external model calls. + ## Run Checks Locally ```bash python -m pip install -r requirements.txt ruff check dare_framework tests --select E9,F63,F7 python -m compileall -q dare_framework tests +pytest -q tests/smoke -m smoke ``` ## Team Agent Rules diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..68d7a186 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,5 @@ +[pytest] +addopts = -ra +testpaths = tests +markers = + smoke: fast deterministic regression checks for CI gate rollout diff --git a/tests/smoke/test_ci_smoke.py b/tests/smoke/test_ci_smoke.py new file mode 100644 index 00000000..faee987c --- /dev/null +++ b/tests/smoke/test_ci_smoke.py @@ -0,0 +1,71 @@ +from __future__ import annotations + +from typing import Any + +import pytest + +from dare_framework.agent import BaseAgent +from dare_framework.infra.component import ComponentType +from dare_framework.model.kernel import IModelAdapter +from dare_framework.model.types import ModelInput, ModelResponse, Prompt + + +class DummyModelAdapter(IModelAdapter): + """Dummy model prevents any external model call during smoke checks.""" + + @property + def name(self) -> str: + return "smoke-dummy" + + @property + def model(self) -> str: + return "smoke-dummy-model" + + @property + def component_type(self) -> ComponentType: + return ComponentType.MODEL_ADAPTER + + async def generate(self, model_input: ModelInput, *, options: Any | None = None) -> ModelResponse: + return ModelResponse(content="smoke-ok") + + +def _base_prompt() -> Prompt: + return Prompt( + prompt_id="smoke.system", + role="system", + content="smoke prompt", + supported_models=["*"], + order=0, + ) + + +async def _build_agent(name: str): + return await ( + BaseAgent.simple_chat_agent_builder(name) + .with_model(DummyModelAdapter()) + .with_prompt(_base_prompt()) + .build() + ) + + +@pytest.mark.asyncio +@pytest.mark.smoke +async def test_smoke_builder_can_build_simple_chat_agent() -> None: + agent = await _build_agent("smoke-builder") + assert agent.name == "smoke-builder" + + +@pytest.mark.asyncio +@pytest.mark.smoke +async def test_smoke_context_assemble_contains_prompt() -> None: + agent = await _build_agent("smoke-context") + assembled = agent.context.assemble() + assert assembled.sys_prompt is not None + assert "smoke prompt" in assembled.sys_prompt.content + + +@pytest.mark.smoke +def test_smoke_prompt_construction() -> None: + prompt = _base_prompt() + assert prompt.role == "system" + assert prompt.content.startswith("smoke")