mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
166 lines
5.2 KiB
Python
166 lines
5.2 KiB
Python
"""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)
|