mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix: post-finale completeness sweep — routing surface, provider config, budgets, compose env, interactive exemption (#661)
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
"""Task.budget_usd / Project.monthly_budget_usd validation (#654).
|
||||
|
||||
The task-budgets feature's own design says "0 rejected — a zero budget
|
||||
silently blocks everything" (every claim is refused from the first tick),
|
||||
so every schema that can set these fields must reject 0 and negative values
|
||||
at the pydantic boundary — a 422, never a stored self-DoS. Null ("no cap")
|
||||
stays valid throughout. Mirrors test_project_sandbox_services.py's style
|
||||
(domain-model `pytest.raises(ValidationError)` coverage).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
from roboco.models.base import Team
|
||||
from roboco.models.project import Project, ProjectCreate, ProjectUpdate
|
||||
from roboco.models.task import Task, TaskUpdate
|
||||
|
||||
|
||||
def _task(budget_usd: float | None = None) -> Task:
|
||||
return Task(
|
||||
title="Add user lookup endpoint",
|
||||
description="Add GET /v1/users/{id} returning user JSON.",
|
||||
acceptance_criteria=["returns 404 for unknown user"],
|
||||
created_by=uuid4(),
|
||||
team=Team.BACKEND,
|
||||
budget_usd=budget_usd,
|
||||
)
|
||||
|
||||
|
||||
def _project(monthly_budget_usd: float | None = None) -> Project:
|
||||
return Project(
|
||||
name="P",
|
||||
slug="p",
|
||||
git_url="https://example.com/r.git",
|
||||
assigned_cell=Team.BACKEND,
|
||||
created_by=uuid4(),
|
||||
monthly_budget_usd=monthly_budget_usd,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Task.budget_usd
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_task_defaults_budget_usd_to_none() -> None:
|
||||
assert _task().budget_usd is None
|
||||
|
||||
|
||||
def test_task_accepts_positive_budget_usd() -> None:
|
||||
budget = 12.5
|
||||
assert _task(budget_usd=budget).budget_usd == budget
|
||||
|
||||
|
||||
def test_task_rejects_zero_budget_usd() -> None:
|
||||
with pytest.raises(ValidationError, match="budget_usd"):
|
||||
_task(budget_usd=0)
|
||||
|
||||
|
||||
def test_task_rejects_negative_budget_usd() -> None:
|
||||
with pytest.raises(ValidationError, match="budget_usd"):
|
||||
_task(budget_usd=-5)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# roboco.models.task.TaskUpdate.budget_usd (domain update model)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_task_update_accepts_null_budget_usd() -> None:
|
||||
assert TaskUpdate(budget_usd=None).budget_usd is None
|
||||
|
||||
|
||||
def test_task_update_accepts_positive_budget_usd() -> None:
|
||||
budget = 3.0
|
||||
assert TaskUpdate(budget_usd=budget).budget_usd == budget
|
||||
|
||||
|
||||
def test_task_update_rejects_zero_budget_usd() -> None:
|
||||
with pytest.raises(ValidationError, match="budget_usd"):
|
||||
TaskUpdate(budget_usd=0)
|
||||
|
||||
|
||||
def test_task_update_rejects_negative_budget_usd() -> None:
|
||||
with pytest.raises(ValidationError, match="budget_usd"):
|
||||
TaskUpdate(budget_usd=-1)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Project.monthly_budget_usd
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_project_defaults_monthly_budget_usd_to_none() -> None:
|
||||
assert _project().monthly_budget_usd is None
|
||||
|
||||
|
||||
def test_project_accepts_positive_monthly_budget_usd() -> None:
|
||||
cap = 100.0
|
||||
assert _project(monthly_budget_usd=cap).monthly_budget_usd == cap
|
||||
|
||||
|
||||
def test_project_rejects_zero_monthly_budget_usd() -> None:
|
||||
with pytest.raises(ValidationError, match="monthly_budget_usd"):
|
||||
_project(monthly_budget_usd=0)
|
||||
|
||||
|
||||
def test_project_rejects_negative_monthly_budget_usd() -> None:
|
||||
with pytest.raises(ValidationError, match="monthly_budget_usd"):
|
||||
_project(monthly_budget_usd=-5)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ProjectCreate.monthly_budget_usd
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_project_create_accepts_null_monthly_budget_usd() -> None:
|
||||
assert (
|
||||
ProjectCreate(
|
||||
name="P",
|
||||
slug="p",
|
||||
git_url="https://example.com/r.git",
|
||||
assigned_cell=Team.BACKEND,
|
||||
).monthly_budget_usd
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_project_create_rejects_zero_monthly_budget_usd() -> None:
|
||||
with pytest.raises(ValidationError, match="monthly_budget_usd"):
|
||||
ProjectCreate(
|
||||
name="P",
|
||||
slug="p",
|
||||
git_url="https://example.com/r.git",
|
||||
assigned_cell=Team.BACKEND,
|
||||
monthly_budget_usd=0,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# ProjectUpdate.monthly_budget_usd
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_project_update_accepts_null_monthly_budget_usd() -> None:
|
||||
assert ProjectUpdate(monthly_budget_usd=None).monthly_budget_usd is None
|
||||
|
||||
|
||||
def test_project_update_accepts_positive_monthly_budget_usd() -> None:
|
||||
cap = 50.0
|
||||
assert ProjectUpdate(monthly_budget_usd=cap).monthly_budget_usd == cap
|
||||
|
||||
|
||||
def test_project_update_rejects_zero_monthly_budget_usd() -> None:
|
||||
with pytest.raises(ValidationError, match="monthly_budget_usd"):
|
||||
ProjectUpdate(monthly_budget_usd=0)
|
||||
|
||||
|
||||
def test_project_update_rejects_negative_monthly_budget_usd() -> None:
|
||||
with pytest.raises(ValidationError, match="monthly_budget_usd"):
|
||||
ProjectUpdate(monthly_budget_usd=-5)
|
||||
Reference in New Issue
Block a user