Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
34 changes: 34 additions & 0 deletions src/capy_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from typing import List, Literal, Optional

from pydantic import BaseModel, Field, model_validator


class CapyStep(BaseModel):
"""capy.yaml step"""

type: Literal["bash", "create-env", "instruction", "wait"] = Field(
description="Type of step to execute"
)
command: Optional[str] = None
text: Optional[str] = None
seconds: Optional[int] = None

@model_validator(mode="after")
def validate_step_payload(self) -> "CapyStep":
"""Reject capy.yaml steps that would fail later during setup."""
if self.type == "bash" and not (self.command and self.command.strip()):
raise ValueError("bash steps require a non-empty command")

if self.type == "instruction" and not (self.text and self.text.strip()):
raise ValueError("instruction steps require non-empty text")

if self.type == "wait" and self.seconds is not None and self.seconds <= 0:
raise ValueError("wait steps require seconds to be greater than 0")

return self


class CapyConfig(BaseModel):
"""capy.yaml config"""

steps: List[CapyStep]
21 changes: 2 additions & 19 deletions src/models.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,12 @@
from typing import List, Literal, Optional
from pydantic import BaseModel, Field, ConfigDict
from pydantic import BaseModel, ConfigDict
from scrapybara.client import Step
from .generate.models import TestCase
from .execute.models import TestResult as BaseTestResult
from .capy_config import CapyConfig, CapyStep
from datetime import datetime


# capy.yaml
class CapyStep(BaseModel):
"""capy.yaml step"""

type: Literal["bash", "create-env", "instruction", "wait"] = Field(
description="Type of step to execute"
)
command: Optional[str] = None
text: Optional[str] = None
seconds: Optional[int] = None


class CapyConfig(BaseModel):
"""capy.yaml config"""

steps: List[CapyStep]


# Review
class TimestampedStep(Step):
"""Base model for all steps in the review process, extending Scrapybara Step"""
Expand Down
45 changes: 45 additions & 0 deletions tests/test_capy_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import pytest
from pydantic import ValidationError

from src.capy_config import CapyConfig


def test_accepts_valid_capy_steps():
config = CapyConfig.model_validate(
{
"steps": [
{"type": "bash", "command": "npm install"},
{"type": "create-env"},
{"type": "instruction", "text": "Open http://localhost:3000"},
{"type": "wait", "seconds": 5},
{"type": "wait"},
]
}
)

assert len(config.steps) == 5


@pytest.mark.parametrize(
("step", "message"),
[
({"type": "bash"}, "bash steps require a non-empty command"),
({"type": "bash", "command": " "}, "bash steps require a non-empty command"),
({"type": "instruction"}, "instruction steps require non-empty text"),
(
{"type": "instruction", "text": ""},
"instruction steps require non-empty text",
),
(
{"type": "wait", "seconds": 0},
"wait steps require seconds to be greater than 0",
),
(
{"type": "wait", "seconds": -1},
"wait steps require seconds to be greater than 0",
),
],
)
def test_rejects_invalid_capy_steps(step, message):
with pytest.raises(ValidationError, match=message):
CapyConfig.model_validate({"steps": [step]})