Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
19 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
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
28 changes: 16 additions & 12 deletions backend/dashboard_metrics/tests/test_tasks.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""Unit tests for Dashboard Metrics Celery tasks."""

import uuid
from datetime import datetime, timedelta

from django.test import TestCase, TransactionTestCase
from django.utils import timezone

from account_v2.models import Organization
from dashboard_metrics.models import (
EventMetricsDaily,
EventMetricsHourly,
Expand Down Expand Up @@ -86,7 +86,10 @@ class TestCleanupTasks(TransactionTestCase):

def setUp(self):
"""Set up test fixtures."""
self.org_id = str(uuid.uuid4())
# organization FK targets Organization's int PK, not a UUID.
self.org = Organization.objects.create(
organization_id="test-org", name="test-org", display_name="Test Org"
)

def test_cleanup_hourly_metrics_deletes_old_records(self):
"""Test that cleanup deletes hourly records older than retention."""
Expand All @@ -96,7 +99,7 @@ def test_cleanup_hourly_metrics_deletes_old_records(self):

# Create old record
EventMetricsHourly.objects.create(
organization_id=self.org_id,
organization=self.org,
timestamp=old_timestamp,
metric_name="old_metric",
metric_type=MetricType.COUNTER,
Expand All @@ -107,7 +110,7 @@ def test_cleanup_hourly_metrics_deletes_old_records(self):

# Create recent record
EventMetricsHourly.objects.create(
organization_id=self.org_id,
organization=self.org,
timestamp=recent_timestamp,
metric_name="recent_metric",
metric_type=MetricType.COUNTER,
Expand All @@ -122,9 +125,10 @@ def test_cleanup_hourly_metrics_deletes_old_records(self):
assert result["deleted"] == 1
assert result["retention_days"] == 30

# Verify old is deleted, recent remains
assert not EventMetricsHourly.objects.filter(metric_name="old_metric").exists()
assert EventMetricsHourly.objects.filter(metric_name="recent_metric").exists()
# _base_manager bypasses the org-scoped default manager, which filters
# by UserContext.get_organization() — None here, so .objects sees nothing.
assert not EventMetricsHourly._base_manager.filter(metric_name="old_metric").exists()
assert EventMetricsHourly._base_manager.filter(metric_name="recent_metric").exists()

def test_cleanup_daily_metrics_deletes_old_records(self):
"""Test that cleanup deletes daily records older than retention."""
Expand All @@ -134,7 +138,7 @@ def test_cleanup_daily_metrics_deletes_old_records(self):

# Create old record
EventMetricsDaily.objects.create(
organization_id=self.org_id,
organization=self.org,
date=old_date,
metric_name="old_daily_metric",
metric_type=MetricType.COUNTER,
Expand All @@ -145,7 +149,7 @@ def test_cleanup_daily_metrics_deletes_old_records(self):

# Create recent record
EventMetricsDaily.objects.create(
organization_id=self.org_id,
organization=self.org,
date=recent_date,
metric_name="recent_daily_metric",
metric_type=MetricType.COUNTER,
Expand All @@ -160,10 +164,10 @@ def test_cleanup_daily_metrics_deletes_old_records(self):
assert result["deleted"] == 1

# Verify old is deleted, recent remains
assert not EventMetricsDaily.objects.filter(
assert not EventMetricsDaily._base_manager.filter(
metric_name="old_daily_metric"
).exists()
assert EventMetricsDaily.objects.filter(
assert EventMetricsDaily._base_manager.filter(
metric_name="recent_daily_metric"
).exists()

Expand All @@ -173,7 +177,7 @@ def test_cleanup_hourly_with_custom_retention(self):
old_timestamp = now - timedelta(days=10)

EventMetricsHourly.objects.create(
organization_id=self.org_id,
organization=self.org,
timestamp=old_timestamp,
metric_name="custom_retention_metric",
metric_type=MetricType.COUNTER,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@
# ---------------------------------------------------------------------------


# Originals displaced by the stubs below, restored once the helper is imported
# so the stubs never leak into sibling test modules' collection (a stubbed
# ``account_v2.models`` would otherwise break their real imports).
_SAVED_MODULES: dict[str, types.ModuleType | None] = {}


def _install(name: str, attrs: dict[str, Any] | None = None) -> types.ModuleType:
"""Install (or replace) a fake module into ``sys.modules``.

Expand All @@ -50,6 +56,7 @@ def _install(name: str, attrs: dict[str, Any] | None = None) -> types.ModuleType
(via pytest collection, conftest, etc.), and we need our fake to
actually take effect.
"""
_SAVED_MODULES.setdefault(name, sys.modules.get(name))
mod = types.ModuleType(name)
if attrs:
for key, value in attrs.items():
Expand All @@ -69,12 +76,32 @@ def _install_package(name: str) -> types.ModuleType:
"""
if name in sys.modules:
return sys.modules[name]
_SAVED_MODULES.setdefault(name, None)
mod = types.ModuleType(name)
mod.__path__ = [] # type: ignore[attr-defined]
sys.modules[name] = mod
return mod


def _restore_modules() -> None:
"""Undo every stub installed above, restoring the real modules (or
removing the stub when nothing was there before). The helper has already
bound its imports by the time this runs, so its tests are unaffected.
"""
for name, original in _SAVED_MODULES.items():
if original is None:
sys.modules.pop(name, None)
else:
sys.modules[name] = original
_SAVED_MODULES.clear()
Comment thread
coderabbitai[bot] marked this conversation as resolved.
# The helper imported above is now cached bound to the stubbed globals.
# Evict it so any later importer in this process gets a real copy; our
# own `_psh_mod`/`PromptStudioHelper` refs are already bound, unaffected.
sys.modules.pop(
"prompt_studio.prompt_studio_core_v2.prompt_studio_helper", None
)


try:
# Account / adapter stubs
_install_package("account_v2")
Expand Down Expand Up @@ -290,6 +317,8 @@ def __init__(self, **kwargs: Any) -> None:
)
PromptStudioHelper = None # type: ignore[assignment]
IKeys = None # type: ignore[assignment]
finally:
_restore_modules()


pytestmark = pytest.mark.skipif(
Expand Down
5 changes: 4 additions & 1 deletion backend/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ dev = [
"responses>=0.25.7",
"psutil>=7.0.0",
]
test = ["pytest>=8.0.1"]
test = [
"pytest>=8.0.1",
"pytest-django>=4.12.0",
]
deploy = [
"gunicorn~=23.0", # For serving the application
# Keep versions empty and let uv decide version
Expand Down
69 changes: 17 additions & 52 deletions backend/usage_v2/tests/test_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,69 +5,34 @@
bare ``"llm"`` bucket from leaking into API deployment responses when
a producer-side LLM call site forgets to set ``llm_usage_reason``.

The tests deliberately do not require a live Django database — the
backend test environment has no ``pytest-django``, no SQLite fallback,
and uses ``django-tenants`` against Postgres in production. Instead
the tests stub ``account_usage.models`` and ``usage_v2.models`` in
``sys.modules`` *before* importing the helper, so the helper module
loads cleanly without triggering Django's app registry checks. The
fake ``Usage.objects.filter`` chain returns a deterministic list of
row dicts shaped exactly like the real ``.values(...).annotate(...)``
queryset rows the helper iterates over.
The tests exercise only the helper's in-memory aggregation logic, not
the ORM. We rebind the ``Usage`` symbol the helper resolved at import
to a fake whose ``objects.filter`` chain returns a deterministic list
of row dicts shaped exactly like the real
``.values(...).annotate(...)`` queryset rows the helper iterates over.
"""

from __future__ import annotations

import sys
import types
from typing import Any
from unittest.mock import MagicMock

import pytest
import usage_v2.helper as helper_mod
from usage_v2.helper import UsageHelper

# ---------------------------------------------------------------------------
# Module-level stubs. Must run BEFORE ``usage_v2.helper`` is imported, so we
# do it at import time and capture the helper reference for the tests below.
# ---------------------------------------------------------------------------


def _install_stubs() -> tuple[Any, Any]:
"""Install fake ``account_usage.models`` and ``usage_v2.models`` modules
so that ``usage_v2.helper`` can be imported without Django being set up.

Returns ``(UsageHelper, FakeUsage)`` — the helper class to test and the
fake Usage class whose ``objects.filter`` we will swap per-test.
"""
# Fake account_usage package + models module
if "account_usage" not in sys.modules:
account_usage_pkg = types.ModuleType("account_usage")
account_usage_pkg.__path__ = [] # mark as package
sys.modules["account_usage"] = account_usage_pkg
if "account_usage.models" not in sys.modules:
account_usage_models = types.ModuleType("account_usage.models")
account_usage_models.PageUsage = MagicMock(name="PageUsage")
sys.modules["account_usage.models"] = account_usage_models

# Fake usage_v2.models with a Usage class whose ``objects`` is a
# MagicMock (so each test can rebind ``filter.return_value``).
if "usage_v2.models" not in sys.modules or not hasattr(
sys.modules["usage_v2.models"], "_is_test_stub"
):
usage_v2_models = types.ModuleType("usage_v2.models")
usage_v2_models._is_test_stub = True

class _FakeUsage:
objects = MagicMock(name="Usage.objects")

usage_v2_models.Usage = _FakeUsage
sys.modules["usage_v2.models"] = usage_v2_models

# Now import the helper — this picks up our stubs.
from usage_v2.helper import UsageHelper

return UsageHelper, sys.modules["usage_v2.models"].Usage
class FakeUsage:
# objects is a MagicMock so each test can rebind filter.return_value.
objects = MagicMock(name="Usage.objects")


UsageHelper, FakeUsage = _install_stubs()
@pytest.fixture(autouse=True)
def _swap_usage(monkeypatch: pytest.MonkeyPatch) -> None:
# Swap the symbol get_usage_by_model resolves, per-test, so monkeypatch
# restores the real model afterwards — a module-level rebind would leak
# FakeUsage into every later test in the same process.
monkeypatch.setattr(helper_mod, "Usage", FakeUsage)


# ---------------------------------------------------------------------------
Expand Down
Empty file.
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

from file_management.exceptions import InvalidFileType
from file_management.file_management_helper import FileManagerHelper
from utils.file_storage.constants import FileStorageConstants, FileStorageKeys
from utils.file_storage.helpers.streaming_writer import write_streaming

from unstract.core.utilities import UnstractUtils
from unstract.sdk1.file_storage import FileStorage
from unstract.sdk1.file_storage.constants import StorageType
from unstract.sdk1.file_storage.env_helper import EnvHelper
from utils.file_storage.constants import FileStorageConstants, FileStorageKeys
from utils.file_storage.helpers.streaming_writer import write_streaming

logger = logging.getLogger(__name__)

Expand Down
18 changes: 17 additions & 1 deletion backend/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 0 additions & 19 deletions platform-service/tests/test_auth_middleware.py

This file was deleted.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ keep-dict-typing = true

[tool.pytest.ini_options]
python_files = ["tests.py", "test_*.py", "*_tests.py"]
DJANGO_SETTINGS_MODULE = "backend.settings.test_cases"
testpaths = ["tests"]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
Expand Down
23 changes: 13 additions & 10 deletions tests/critical_paths.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@
# A "critical path" is an end-to-end user or system flow whose failure would
# constitute a production incident. The rig reports:
# ✅ covered — at least one group in `covered_by` ran green this build
# ⚠️ gap — `covered_by` is empty OR no group covering it ran
# ⚠️ gap — no covering group ran green this build
# ❌ regression — a path that was ✅ on the cached main baseline is now not ✅
#
# Only one kind of gap gates --fail-on-critical-gap:
# • in-scope gap — a covering group ran in this tier but not green; fails.
# • out-of-scope gap — covered only by an unrun tier, or no group declared;
# warn-only (a tier can't fail for coverage it can't run).
#
# Only wire `covered_by` to a group that really exercises the path — a bogus
# mapping fails the build when that group breaks, for the wrong reason.
#
# We intentionally do NOT chase 100% coverage. Focus on filling these gaps first.
version: 1

Expand All @@ -21,10 +29,10 @@ paths:
- id: adapter-register-llm
description: "Register and validate an LLM adapter."
entry: "POST /api/v1/adapter/"
# Honest declaration: unit-backend is currently optional/gated and
# e2e-smoke only hits /health/. Track as a gap until a real adapter test
# exists (likely under tests/e2e/smoke/ or a new tests/e2e/adapters/ group).
covered_by: []
# unit-sdk1 covers adapter registration + parameter validation at the SDK
# layer (the logic the endpoint delegates to). The HTTP round-trip stays an
# e2e concern; promote when an e2e adapter group exists.
covered_by: [unit-sdk1]

- id: workflow-create-execute
description: "Create a workflow, configure source+destination, execute, poll, fetch result."
Expand All @@ -46,11 +54,6 @@ paths:
entry: "POST /api/v1/pipeline/{id}/execute/"
covered_by: [] # gap

- id: tool-sandbox-exec
description: "Tool image runs in sandbox container and emits structured output."
entry: "internal: tool-registry → runner → docker run"
covered_by: [unit-runner]

- id: usage-token-tracking
description: "Per-execution token usage is recorded and retrievable."
entry: "GET /api/v1/usage/get_token_usage/"
Expand Down
Loading
Loading