Просмотр исходного кода

fix(maintenance): #1596 persist wiki_url on Custom Type create + correct
inline mutation type

POST /api/v1/maintenance/types hard-coded the MaintenanceType
constructor and silently dropped `wiki_url`, so the Documentation URL
field disappeared after save. PATCH worked because it uses
`data.model_dump(exclude_unset=True) + setattr`, which is why editing
a freshly-created type DID save the URL — masking the bug under any
"save then immediately fix it" retest. Reporter @BurntOutHylian
pre-triaged the issue to the exact constructor call at
routes/maintenance.py:206-213; fix is the missing `wiki_url=data.wiki_url`
argument.

Frontend nit from the same report: MaintenancePage.tsx:1131's
`updateTypeMutation` declared `data: Partial<{ name; default_interval_hours;
interval_type; icon }>` — omitting `wiki_url`. The value reached the
API correctly at runtime because `api.updateMaintenanceType` accepts
`Partial<MaintenanceTypeCreate>` (which has wiki_url), but the inline
type lied about the payload shape. Extended the inline `Partial<{...}>`
to include `wiki_url?: string | null`. Pure type fix — no runtime change.

maziggy 1 день назад
Родитель
Сommit
673001e3cd

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
CHANGELOG.md


+ 1 - 0
backend/app/api/routes/maintenance.py

@@ -209,6 +209,7 @@ async def create_maintenance_type(
         default_interval_hours=data.default_interval_hours,
         default_interval_hours=data.default_interval_hours,
         interval_type=data.interval_type,
         interval_type=data.interval_type,
         icon=data.icon,
         icon=data.icon,
+        wiki_url=data.wiki_url,
         is_system=False,
         is_system=False,
     )
     )
     db.add(new_type)
     db.add(new_type)

+ 25 - 0
backend/tests/integration/test_maintenance_api.py

@@ -46,6 +46,31 @@ class TestMaintenanceTypesAPI:
         assert result["name"] == "Custom Test Task"
         assert result["name"] == "Custom Test Task"
         assert result["is_system"] is False
         assert result["is_system"] is False
 
 
+    @pytest.mark.asyncio
+    @pytest.mark.integration
+    async def test_create_custom_type_persists_wiki_url(self, async_client: AsyncClient):
+        """#1596: pre-fix, the POST handler hard-coded every constructor field
+        by name and silently dropped `wiki_url`. The schema accepted the value,
+        the response echoed `null`, and the row landed without it. Pin the
+        contract so the constructor doesn't drift again."""
+        data = {
+            "name": "Wiki URL Persistence Test",
+            "default_interval_hours": 50.0,
+            "interval_type": "hours",
+            "wiki_url": "https://wiki.example.com/lubrication",
+        }
+        response = await async_client.post("/api/v1/maintenance/types", json=data)
+        assert response.status_code == 200
+        assert response.json()["wiki_url"] == "https://wiki.example.com/lubrication"
+
+        # Verify it persists through a separate GET round-trip — the POST
+        # response could have echoed the request body without committing.
+        list_response = await async_client.get("/api/v1/maintenance/types")
+        assert list_response.status_code == 200
+        matching = [t for t in list_response.json() if t["name"] == data["name"]]
+        assert len(matching) == 1
+        assert matching[0]["wiki_url"] == "https://wiki.example.com/lubrication"
+
     @pytest.mark.asyncio
     @pytest.mark.asyncio
     @pytest.mark.integration
     @pytest.mark.integration
     async def test_update_maintenance_type(self, async_client: AsyncClient):
     async def test_update_maintenance_type(self, async_client: AsyncClient):

+ 5 - 1
frontend/src/pages/MaintenancePage.tsx

@@ -1128,7 +1128,11 @@ export function MaintenancePage() {
   // directly in onAddType callback
   // directly in onAddType callback
 
 
   const updateTypeMutation = useMutation({
   const updateTypeMutation = useMutation({
-    mutationFn: ({ id, data }: { id: number; data: Partial<{ name: string; default_interval_hours: number; interval_type: 'hours' | 'days'; icon: string }> }) =>
+    // `wiki_url` is part of `MaintenanceTypeCreate` and reaches the API
+    // correctly at runtime (the api helper takes `Partial<MaintenanceTypeCreate>`),
+    // but the inline shape on this mutation used to omit it — making the
+    // type lie about what the payload carries (#1596 nit).
+    mutationFn: ({ id, data }: { id: number; data: Partial<{ name: string; default_interval_hours: number; interval_type: 'hours' | 'days'; icon: string; wiki_url: string | null }> }) =>
       api.updateMaintenanceType(id, data),
       api.updateMaintenanceType(id, data),
     onSuccess: () => {
     onSuccess: () => {
       queryClient.invalidateQueries({ queryKey: ['maintenanceTypes'] });
       queryClient.invalidateQueries({ queryKey: ['maintenanceTypes'] });

Некоторые файлы не были показаны из-за большого количества измененных файлов