Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bdfb375
UN-3632 [FIX] Scope rig --fail-on-critical-gap to in-tier coverage so…
chandrasekharan-zipstack Jun 2, 2026
54203db
test: prune dead rig groups/paths, park deprecated services, wire bac…
chandrasekharan-zipstack Jun 29, 2026
8f2db83
fix: make rig editable-install survive uv run re-sync; drop phantom D…
chandrasekharan-zipstack Jun 29, 2026
af0af8e
test: make unit-backend collect + run django_db tests in the rig
chandrasekharan-zipstack Jun 29, 2026
7aff271
test: fix two pre-existing backend test bugs exposed by the rig
chandrasekharan-zipstack Jun 29, 2026
a75f0e3
test: gate live connector integration tests behind credential env vars
chandrasekharan-zipstack Jun 29, 2026
443cbe1
test: make unit-core and unit-connectors required rig groups
chandrasekharan-zipstack Jun 29, 2026
aa11380
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 29, 2026
9e04f44
test: address PR review feedback on rig + connector test guards
chandrasekharan-zipstack Jun 30, 2026
674fe8a
test: provision infra for integration tier; split DB/credential tests…
chandrasekharan-zipstack Jun 30, 2026
327b68e
test: address PR review — wire provisioned Redis, mark http_fs integr…
chandrasekharan-zipstack Jul 1, 2026
2f5784f
test: use hostname not literal IP in redis-wiring test (Sonar hotspot)
chandrasekharan-zipstack Jul 1, 2026
c214d34
test: mark local testcontainers MinIO http endpoint NOSONAR
chandrasekharan-zipstack Jul 1, 2026
da48e89
test: stub UserDefaultAdapter so prompt-studio build-index tests run
chandrasekharan-zipstack Jul 1, 2026
58a96ab
test: drop prompt-service from test compose overlay
chandrasekharan-zipstack Jul 1, 2026
56d151e
test: remove dead S3 smoke test and strip print() debug from connecto…
chandrasekharan-zipstack Jul 1, 2026
7d17ba9
test: switch unit-backend to marker-based selection; classify integra…
chandrasekharan-zipstack Jul 1, 2026
6d61a56
test: centralize DB-test marking; cover adapter-register-llm via inte…
chandrasekharan-zipstack Jul 2, 2026
6c1a9e3
test: complete DB-test centralization; move DB-writer tests to integr…
chandrasekharan-zipstack Jul 2, 2026
1cc8286
test: switch integration-backend to marker-only selection; drop dead …
chandrasekharan-zipstack Jul 7, 2026
337dd34
docs: concise backend test-contribution steps in tests/README.md
chandrasekharan-zipstack Jul 7, 2026
446d5d4
Merge remote-tracking branch 'origin/main' into feat/rig-integration-…
chandrasekharan-zipstack Jul 7, 2026
c9eae7c
test: rewrite deployment_helper staging tests with patch.object
chandrasekharan-zipstack Jul 7, 2026
9a76a74
test: drop sys.modules stub ceremony from build-index tests; subproce…
chandrasekharan-zipstack Jul 7, 2026
cbbb780
ci: move path filtering from trigger to job level for required-check …
chandrasekharan-zipstack Jul 7, 2026
913379a
test: standardize pytest python_files across services; drop no-flask …
chandrasekharan-zipstack Jul 7, 2026
437d5b0
test: collect Django per-app tests.py files
chandrasekharan-zipstack Jul 7, 2026
98e9da4
ci: run unit and integration tiers as parallel jobs
chandrasekharan-zipstack Jul 7, 2026
7ca9298
ci: make report job propagate tier failures
chandrasekharan-zipstack Jul 7, 2026
c17efc1
chore: tighten comments added in this PR
chandrasekharan-zipstack Jul 7, 2026
d437e71
ci: enforce --fail-on-critical-gap on PRs too
chandrasekharan-zipstack Jul 7, 2026
f7bdd06
ci: run tests on PRs against any base branch
chandrasekharan-zipstack Jul 7, 2026
c9ac28e
ci: tighten workflow comments; drop comment on removed trigger filter
chandrasekharan-zipstack Jul 7, 2026
3b56a71
test: add per-test critical-path marker proof; stop 'empty' attesting…
chandrasekharan-zipstack Jul 8, 2026
47a5112
test: restore mock-only connector tests + error-path test; fail repor…
chandrasekharan-zipstack Jul 8, 2026
7212b13
refactor: address SonarCloud issues on rig code
chandrasekharan-zipstack Jul 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 142 additions & 41 deletions .github/workflows/ci-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,47 @@ on:
push:
branches:
- main
paths-ignore:
- "docker/**"
- "frontend/**"
- "docs/**"
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
branches: [main]
paths-ignore:
- "docker/**"
- "frontend/**"
- "docs/**"

jobs:
# Path filtering at job level, not trigger paths-ignore: an untriggered
# workflow reports no check run, so a required check waits forever; a job
# skipped via `if:` reports conclusion `skipped`, which passes.
changes:
runs-on: ubuntu-latest
outputs:
relevant: ${{ steps.filter.outputs.relevant }}
steps:
- name: Checkout repository
# paths-filter needs the local git history for push events.
uses: actions/checkout@v6

- name: Check for changes outside ignored paths
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: filter
with:
# `every`: a file counts as relevant only if it matches ALL the
# negated globs, i.e. lies outside every ignored dir.
predicate-quantifier: every
filters: |
relevant:
- "!docker/**"
- "!frontend/**"
- "!docs/**"

# Tiers share no state (separate runners, disjoint groups), so they run as
# parallel matrix legs; whatever needs both tiers' results happens in `report`.
test:
if: github.event.pull_request.draft == false
needs: changes
if: needs.changes.outputs.relevant == 'true' && github.event.pull_request.draft == false
runs-on: ubuntu-latest
strategy:
# One tier failing must not cancel the other; the report job needs
# whatever results exist from both.
fail-fast: false
matrix:
tier: [unit, integration]
Comment thread
chandrasekharan-zipstack marked this conversation as resolved.

steps:
- name: Checkout repository
Expand All @@ -37,21 +62,20 @@ jobs:
uses: actions/cache@v5
with:
path: .tox/
key: ${{ runner.os }}-tox-uv-${{ hashFiles('**/pyproject.toml', '**/tox.ini') }}
# Tier in the key: legs build different tox envs, and parallel
# saves to one key race (first save wins).
key: ${{ runner.os }}-tox-uv-${{ matrix.tier }}-${{ hashFiles('**/pyproject.toml', '**/tox.ini') }}
restore-keys: |
${{ runner.os }}-tox-uv-${{ matrix.tier }}-
${{ runner.os }}-tox-uv-

- name: Restore main-branch test baseline (for regression detection)
# actions/cache only saves on a cache miss. Include the run id in the
# key so each main build writes a fresh cache entry; the prefix in
# restore-keys pulls the most recent baseline.
#
# The unit/integration lane keeps a SEPARATE baseline from the e2e
# workflow because their `scope_groups` don't overlap — restoring an
# e2e-tier baseline here would flag every e2e-covered path as a
# regression in this lane (and vice versa). Each workflow is the
# source of truth for the paths covered by its own tiers.
uses: actions/cache@v5
# Restore-only: the report job is the sole baseline writer (a save
# here would claim this run's key with an unmerged file). Baseline is
# separate from the e2e workflow's — their scope_groups don't overlap,
# and a cross-lane restore would flag the other lane's paths as
# regressions.
uses: actions/cache/restore@v5
with:
path: reports/previous-summary.json
key: unstract-test-baseline-ut-main-${{ github.run_id }}
Expand All @@ -64,35 +88,105 @@ jobs:

- name: Validate test manifests
# Cheap pre-flight: catches groups.yaml / critical_paths.yaml schema
# errors before we spend minutes on tier runs. Also catches malformed
# manifests on PRs that only touch paths-ignored files (because this
# step always runs).
# errors before we spend minutes on tier runs.
run: tox -e rig -- validate

- name: Run unit tier
# Each tier runs as a separate rig invocation so its results land in
# reports/<group>/ before the next tier starts. --update-baseline (on
# main only) merges this tier's covered paths into the cached
# previous-summary.json; later tiers union on top.
- name: Run ${{ matrix.tier }} tier
# Gap check on PRs too: main is gap-free and PRs test the merge ref,
# so a gap here can only be PR-introduced. No --update-baseline:
# parallel writes would race; the report job merges once (main only).
run: tox -e ${{ matrix.tier }} -- --fail-on-critical-gap

- name: Suffix coverage data for cross-tier merge
# Both tiers emit a bare reports/.coverage that would clobber on
# download; suffixed names match the `.coverage.*` glob `report
# combine` unions.
if: always()
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
tox -e unit -- --fail-on-critical-gap --update-baseline
else
tox -e unit
if [ -f reports/.coverage ]; then
mv reports/.coverage "reports/.coverage.tier-${{ matrix.tier }}"
fi

- name: Run integration tier
- name: Upload tier reports artifact
if: always()
uses: actions/upload-artifact@v4
with:
name: reports-${{ matrix.tier }}
# Baseline excluded: it's cache-managed, and a stale copy inside the
# artifact would overwrite the report job's freshly restored one.
path: |
reports/
!reports/previous-summary.json
if-no-files-found: ignore
retention-days: 14

report:
needs: [changes, test]
# `always()` so a red tier still gets a PR report. Skip only a legitimate
# skip — irrelevant paths / draft, decided by a *successful* changes job.
# A failed changes job skips the tiers, so this must run and go red
# (final step) instead of green-skipping the required check.
if: >-
always() && (needs.changes.result != 'success'
|| (needs.changes.outputs.relevant == 'true' && github.event.pull_request.draft == false))
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Install uv
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
with:
version: "0.6.14"
python-version: 3.12.9

- name: Cache tox environments
uses: actions/cache@v5
with:
path: .tox/
key: ${{ runner.os }}-tox-uv-rig-${{ hashFiles('**/pyproject.toml', '**/tox.ini') }}
restore-keys: |
${{ runner.os }}-tox-uv-rig-
${{ runner.os }}-tox-uv-

- name: Restore main-branch test baseline
uses: actions/cache/restore@v5
with:
path: reports/previous-summary.json
key: unstract-test-baseline-ut-main-${{ github.run_id }}
restore-keys: |
unstract-test-baseline-ut-main-
unstract-test-baseline-ut-

- name: Install tox with UV
run: uv tool install tox --with tox-uv

- name: Download tier reports
uses: actions/download-artifact@v4
with:
pattern: reports-*
path: reports/
merge-multiple: true

- name: Combine reports from both tiers
# On green main builds also merge covered paths into the baseline
# (single writer; the rig refuses the merge if any group is red).
run: |
if [ "${{ github.ref }}" = "refs/heads/main" ]; then
tox -e integration -- --fail-on-critical-gap --update-baseline
else
tox -e integration
flags=""
if [ "${{ github.ref }}" = "refs/heads/main" ] && [ "${{ needs.test.result }}" = "success" ]; then
flags="--update-baseline"
fi
tox -e rig -- report combine $flags

- name: Re-aggregate reports from both tiers
if: always()
run: tox -e rig -- report combine
- name: Save updated baseline
# actions/cache saves only on a miss; the run id keys each main build
# a fresh entry, and restore-keys' prefix pulls the latest next run.
if: github.ref == 'refs/heads/main'
uses: actions/cache/save@v5
with:
path: reports/previous-summary.json
key: unstract-test-baseline-ut-main-${{ github.run_id }}

- name: Render combined test report to PR
uses: marocchino/sticky-pull-request-comment@70d2764d1a7d5d9560b100cbea0077fc8f633987 # v3.0.2
Expand All @@ -116,3 +210,10 @@ jobs:
path: reports/
if-no-files-found: ignore
retention-days: 14

- name: Fail if any tier failed
# Lets this job serve as the single branch-protection check: a matrix
# `result` is success only when every leg passed.
if: always()
run: |
[ "${{ needs.test.result }}" = "success" ] || exit 1
61 changes: 61 additions & 0 deletions backend/adapter_processor_v2/tests/test_adapter_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Critical path ``adapter-register-llm``: POST /api/v1/adapter/ registers an
LLM adapter. Exercises the real endpoint wiring — auth, serializer, metadata
encryption, org-scoped persistence — with only the SDK context-window lookup
(a provider-shaped call) mocked. Needs a live DB (integration tier).
"""

from __future__ import annotations

import secrets
from unittest.mock import patch

import pytest
from account_v2.models import Organization, User
from django.test import TestCase
Comment thread
chandrasekharan-zipstack marked this conversation as resolved.
from rest_framework import status
from rest_framework.test import APIRequestFactory, force_authenticate
from tenant_account_v2.models import OrganizationMember
from utils.user_context import UserContext

from adapter_processor_v2.models import AdapterInstance
from adapter_processor_v2.views import AdapterInstanceViewSet


class AdapterRegisterLLMAPITest(TestCase):
def setUp(self) -> None:
self.org = Organization.objects.create(
name="org-a", display_name="Org A", organization_id="org-a"
)
UserContext.set_organization_identifier(self.org.organization_id)
self.user = User.objects.create_user(
username="owner@example.com",
email="owner@example.com",
password=secrets.token_urlsafe(),
)
OrganizationMember.objects.create(
organization=self.org, user=self.user, role="user"
)
self.create_view = AdapterInstanceViewSet.as_view({"post": "create"})

@pytest.mark.critical_path("adapter-register-llm")
@patch.object(AdapterInstance, "get_context_window_size", return_value=4096)
def test_register_llm_adapter_persists_encrypted(self, _ctx_window) -> None:
payload = {
"adapter_id": "openai|test-llm",
"adapter_name": "my-openai",
"adapter_type": "LLM",
"adapter_metadata": {"api_key": "sk-test", "model": "gpt-4o-mini"},
}
request = APIRequestFactory().post("/api/v1/adapter/", payload, format="json")
force_authenticate(request, user=self.user)

response = self.create_view(request)

assert response.status_code == status.HTTP_201_CREATED, response.data
instance = AdapterInstance.objects.get(adapter_name="my-openai")
# persisted under the request user's org, created_by the request user
assert instance.organization_id == self.org.id
assert instance.created_by == self.user
# metadata stored encrypted (binary), decrypts back via .metadata
assert instance.adapter_metadata_b is not None
assert instance.metadata["model"] == "gpt-4o-mini"
Loading
Loading