From 49fddd1dc11c2a051dd70c950bf878f854c04eda Mon Sep 17 00:00:00 2001 From: Renn F Date: Wed, 3 Jun 2026 20:42:15 +0200 Subject: [PATCH] fix(project): default_branch defaults to master across the API create path The request and service models defaulted default_branch to "main" and the response converters fell back to "main", so omitting the field on the create route persisted "main" instead of the DB column default of master. Flip every default_branch default and fallback to master. --- roboco/api/schemas/project.py | 6 +++--- roboco/models/project.py | 4 ++-- tests/integration/test_project_routes.py | 23 +++++++++++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/roboco/api/schemas/project.py b/roboco/api/schemas/project.py index 0c32d765..940ae112 100644 --- a/roboco/api/schemas/project.py +++ b/roboco/api/schemas/project.py @@ -84,7 +84,7 @@ class ProjectCreateRequest(BaseModel): name: str = Field(..., min_length=1, max_length=100) slug: str = Field(..., min_length=1, max_length=50, pattern=r"^[a-z0-9-]+$") git_url: str - default_branch: str = "main" + default_branch: str = "master" protected_branches: list[str] | None = Field( default=None, description="Branches to protect. Defaults to [default_branch].", @@ -156,7 +156,7 @@ def project_to_response(project: "ProjectTable") -> ProjectResponse: name=str(project.name), slug=str(project.slug), git_url=str(project.git_url), - default_branch=str(default_branch) if default_branch else "main", + default_branch=str(default_branch) if default_branch else "master", protected_branches=list(project.protected_branches or []), assigned_cell=project.assigned_cell, has_git_token=bool(project.git_token_encrypted), @@ -183,7 +183,7 @@ def project_to_summary(project: "ProjectTable") -> ProjectSummaryResponse: name=str(project.name), slug=str(project.slug), git_url=str(project.git_url), - default_branch=str(default_branch) if default_branch else "main", + default_branch=str(default_branch) if default_branch else "master", assigned_cell=project.assigned_cell, is_active=bool(project.is_active), has_workspace=bool(project.workspace_path), diff --git a/roboco/models/project.py b/roboco/models/project.py index f98299e8..83efb4fc 100644 --- a/roboco/models/project.py +++ b/roboco/models/project.py @@ -46,7 +46,7 @@ class Project(TimestampMixin): # Git Configuration git_url: str = Field(..., description="Git repository URL") - default_branch: str = Field(default="main", description="Default branch name") + default_branch: str = Field(default="master", description="Default branch name") protected_branches: list[str] = Field( default_factory=lambda: ["main", "master"], description="Branches that cannot be pushed to directly", @@ -104,7 +104,7 @@ class ProjectCreate(RobocoBase): name: str = Field(..., min_length=1, max_length=100) slug: str = Field(..., min_length=1, max_length=50, pattern=r"^[a-z0-9-]+$") git_url: str - default_branch: str = "main" + default_branch: str = "master" protected_branches: list[str] = Field(default_factory=lambda: ["main", "master"]) assigned_cell: Team diff --git a/tests/integration/test_project_routes.py b/tests/integration/test_project_routes.py index 197ad80f..97f45b67 100644 --- a/tests/integration/test_project_routes.py +++ b/tests/integration/test_project_routes.py @@ -156,6 +156,29 @@ async def test_list_projects_filter_by_cell( assert response.status_code == HTTPStatus.OK +@pytest.mark.asyncio +async def test_create_project_omitting_default_branch_yields_master( + project_client: AsyncClient, +) -> None: + """Omitting ``default_branch`` on the create route resolves to ``master``. + + Exercises the real API create path: ``ProjectCreateRequest`` (field + omitted) -> route ``create_project`` -> ``ProjectCreate`` -> service + ``create`` -> ``ProjectTable`` -> ``project_to_response``. None of these + must inject the legacy ``main`` default. + """ + payload = _payload() + del payload["default_branch"] + + response = await project_client.post("/api/projects", json=payload, headers=_HDR) + assert response.status_code == HTTPStatus.CREATED + assert response.json()["default_branch"] == "master" + + fetched = await project_client.get(f"/api/projects/{payload['slug']}", headers=_HDR) + assert fetched.status_code == HTTPStatus.OK + assert fetched.json()["default_branch"] == "master" + + @pytest.mark.asyncio async def test_list_projects_includes_default_branch( project_client: AsyncClient,