model_assignments / chat_models /
agent_models. Замок держит БД (FK RESTRICT),
не только проверка в коде — тест обходит сервисный слой и бьёт прямо в
базу.
Platform-провайдер,
встроенные модели, tool-пресеты; не хардкод-фикстурой
@pytest.mark.integration — весь слой требует БД;
приоритет (p0 / p1) ортогонально типу
model_assignments → DELETE ai_models отбит (FK RESTRICT → IntegrityError)chat_models → удаление / выключение отбито RESTRICTagent_models → отбито тем же RESTRICTis_default=true в chat_models отбит partial uniqueis_default, не назначив новый → запрет (инвариант «ровно один» доводит приложение)agent_models — форма списка общаяai_providers → его ai_models уходят по ON DELETE CASCADECHECK (kind='cloud' OR base_url IS NOT NULL) — local без endpoint отвергается БДUNIQUE(provider_id, model_id) — тот же model_id у провайдера дважды отбит••••xxxx, не api_key_enc и не открытый ключmodel_assignments.function CHECK-ается словарём {harvester_embedding · query_rag}; INSERT с function='chat' / 'agent_engine' → отбит БД (назначение возможно лишь на системные функции)model_usage.function='chat' проходит: расход учитывается по всем четырём функциям, включая chat / agent_engine; два разных словаря на одну колонку → Словарь функцийUNIQUE(model_id, function, bucket_date) → одна строка, счётчики (request_count · input_tokens · output_tokens) растутfunction / bucket_date → отдельная строка агрегатаmodel_id обнуляется по ON DELETE SET NULL, строка расхода живёт — cost переживаетcost = input·price_input + output·price_output; хоть одна цена NULL → cost NULLUNIQUE(name) — дубль ключа инструмента отбитcredential_enc наружу не уходит — только флаг is_set; config (JSONB) несекретный, отдаётся как естьsource ∈ (preset · custom · mcp · openapi) — иное отвергается БДaccess ∈ (read_only · write) — иное отвергаетсяis_system-провайдера Platform — неудаляемis_system-провайдера Platform в обход сервисного слоя — прямой DELETE в базу отбит замком БД, не только проверкой в кодеmeta: bge-m3 {embedding_dim:1024, max_input_tokens:8192}, Qwen3-Embedding-0.6B {1024, 32768}web_search · fetch_url), оба chat_enabled=false и agents_allowed=false••••xxxx / флаг is_set, не секрет200, старый снят атомарно409, не 500 (замок занятой модели)base_url, дубль model_id, кривой source/access инструмента → 422, не 500model_type гейтит: embedding-модель в chat_models/agent_models или на query_rag → 422; chat-модель на embedding-функцию → 422 — назначение по чужому типу отбито, не 500 → Тип решает назначениеGET/PATCH/DELETE провайдера / модели / инструмента по несуществующему {id} → 404, не 500Member на POST/PATCH/DELETE реестра (провайдеры · модели · инструменты · назначения) → 403; аноним → 401; параметризовано по всем write-роутам, не только discovery/v1/models) или local (Ollama /api/tags) провайдера; discovery — конфиг, не инференс500200; Member → 403; аноним → 401 (параметризовано по роутам реестра)CHECK (id = 1), override / reset / кап
токенов — покрыты со стороны Admin
(test_prompt_settings.py);
здесь не дублируется
Весь слой — integration с БД. Приоритет (P0–P1) ортогонален каталогам и
задаётся маркерами (pytest -m p0), а не отдельными папками.
tests/ai_foundation/каталог модуля
conftest.pytestcontainers PG, фабрики провайдер / модельintegration/с БД, per-test rollback
model-lifecycle-lockзамок RESTRICT занятой моделиdefault-model · provider-cascadeровно один default, каскад / CHECK / маскаmodel-usage-upsert · tools-registryupsert-расход, каталог инструментовseed-migrationзасев + обратимость downgrade