From 88e41e339f8243e63a79481c7ee5232ea9131a5f Mon Sep 17 00:00:00 2001 From: mikemolinet Date: Tue, 5 May 2026 17:41:49 -0700 Subject: [PATCH 1/2] ci(test): spin up cueapi-core locally instead of hitting staging with ARGUS_STAGING_KEY MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `test` job's `pytest tests/` runs the SDK's CRUD tests against a real CueAPI server. Previously that server was remote staging, authenticated by `secrets.ARGUS_STAGING_KEY`. Argus was retired 2026-05-02 (cueapi PR #539) and that key/user is no longer valid — every PR's `test` job has been red for ~3 days with `AuthenticationError: Invalid API key`, blocking PRs #30 and #31 (and any future SDK PRs). This switches the job to the same self-contained pattern that's already proven by the passing `sdk-integration` job: clone cueapi-core, install, migrate, boot uvicorn locally, register a fresh test user via POST /v1/auth/register (gated by ALLOW_REGISTER=true), capture the key, plumb it through the existing `CUEAPI_STAGING_URL` / `CUEAPI_STAGING_API_KEY` env vars (no SDK code change needed — `tests/conftest.py` already reads them from env). Workflow-only diff. No SDK behavior change. Note: `notify-merge` still references `secrets.ARGUS_CUEAPI_KEY` for the post-merge prod telemetry cue. That key is also stale, but the step runs after auto-merge so it doesn't gate the PR — leaving for a follow-up that needs a new prod key minted by an operator. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/feature-to-main.yml | 58 +++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/.github/workflows/feature-to-main.yml b/.github/workflows/feature-to-main.yml index 7168ce9..e3d768f 100644 --- a/.github/workflows/feature-to-main.yml +++ b/.github/workflows/feature-to-main.yml @@ -53,17 +53,69 @@ jobs: pip install -r cueapi-core/requirements.txt pip install -e . - - name: Run tests + - name: Apply migrations to test DB + working-directory: cueapi-core env: DATABASE_URL: postgresql+asyncpg://runner@localhost:5432/cueapi_test REDIS_URL: redis://localhost:6379 SESSION_SECRET: test-session-secret-32-chars-minimum!! ENV: test RESEND_API_KEY: "" - CUEAPI_STAGING_URL: https://api-staging-e962.up.railway.app - CUEAPI_STAGING_API_KEY: ${{ secrets.ARGUS_STAGING_KEY }} + run: python -m alembic upgrade head + + - name: Start cueapi-core API in background + working-directory: cueapi-core + env: + DATABASE_URL: postgresql+asyncpg://runner@localhost:5432/cueapi_test + REDIS_URL: redis://localhost:6379 + SESSION_SECRET: test-session-secret-32-chars-minimum!! + ENV: test + RESEND_API_KEY: "" + ALLOW_REGISTER: "true" + BASE_URL: http://localhost:8000 + run: | + nohup python -m uvicorn app.main:app --host 127.0.0.1 --port 8000 \ + > /tmp/uvicorn.log 2>&1 & + echo $! > /tmp/uvicorn.pid + + - name: Wait for API health + run: | + for i in $(seq 1 30); do + if curl -sf http://127.0.0.1:8000/health > /dev/null; then + echo "API ready after ${i}s" + exit 0 + fi + sleep 1 + done + echo "API failed to start within 30s" + cat /tmp/uvicorn.log + exit 1 + + - name: Register test user and capture API key + id: register_user + run: | + EMAIL="ci-sdk-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}@example.com" + RESP=$(curl -sf -X POST http://127.0.0.1:8000/v1/auth/register \ + -H "Content-Type: application/json" \ + -d "{\"email\":\"${EMAIL}\"}") + KEY=$(printf '%s' "$RESP" | python -c 'import sys,json;print(json.load(sys.stdin)["api_key"])') + if [ -z "$KEY" ]; then + echo "Registration failed; response: $RESP" + exit 1 + fi + echo "::add-mask::$KEY" + echo "api_key=$KEY" >> "$GITHUB_OUTPUT" + + - name: Run tests + env: + CUEAPI_STAGING_URL: http://127.0.0.1:8000 + CUEAPI_STAGING_API_KEY: ${{ steps.register_user.outputs.api_key }} run: pytest tests/ -v --tb=short + - name: Dump uvicorn log on failure + if: failure() + run: cat /tmp/uvicorn.log || true + auto-merge: needs: test runs-on: ubuntu-latest From de3192ce5ad6435b24deed7d299bd25302890c0b Mon Sep 17 00:00:00 2001 From: mikemolinet Date: Tue, 5 May 2026 17:45:15 -0700 Subject: [PATCH 2/2] ci(test): replace alembic with Base.metadata.create_all (matches cueapi-core conftest) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First push failed because OSS migration set ends at 023 while the User model declares an `api_key_encrypted` column with no migration backing it. That's a parity drift in cueapi-core (private migration 019 in the hosted repo includes the column; the OSS port renamed/replaced it with the alert-webhook bits but kept the column on the model). `alembic upgrade head` produced a schema missing that column → register endpoint 500'd on the User SELECT. Switch the CI bootstrap to model-driven schema init via `Base.metadata.create_all`, which is the exact pattern cueapi-core's own `tests/conftest.py` uses (and which is robust to model/migration drift because the model is the source of truth for tests). Imports the same model list as conftest so all tables register before create_all runs. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/feature-to-main.yml | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/.github/workflows/feature-to-main.yml b/.github/workflows/feature-to-main.yml index e3d768f..643240c 100644 --- a/.github/workflows/feature-to-main.yml +++ b/.github/workflows/feature-to-main.yml @@ -53,7 +53,7 @@ jobs: pip install -r cueapi-core/requirements.txt pip install -e . - - name: Apply migrations to test DB + - name: Initialize test DB schema (model-driven, matches cueapi-core/tests/conftest.py) working-directory: cueapi-core env: DATABASE_URL: postgresql+asyncpg://runner@localhost:5432/cueapi_test @@ -61,7 +61,27 @@ jobs: SESSION_SECRET: test-session-secret-32-chars-minimum!! ENV: test RESEND_API_KEY: "" - run: python -m alembic upgrade head + run: | + python <<'PY' + import asyncio + from sqlalchemy.ext.asyncio import create_async_engine + from app.config import settings + from app.database import Base + # Import all models so Base.metadata sees them (mirrors tests/conftest.py). + from app.models import ( # noqa: F401 + Alert, Cue, DispatchOutbox, Execution, UsageMonthly, User, Worker, DeviceCode, + Agent, Message, UsageMessagesMonthly, + ) + + async def _init(): + engine = create_async_engine(settings.DATABASE_URL) + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + await engine.dispose() + + asyncio.run(_init()) + print("Schema initialized via Base.metadata.create_all") + PY - name: Start cueapi-core API in background working-directory: cueapi-core