فهرست منبع

fix(api): allow creating a policy without thresholds

The UI's create form intentionally omits the threshold matrix and
points users to the edit page after creation, but the API rejected
that body with `thresholds: required`. Per SPEC §4 a policy with
zero thresholds is valid (absent row = category not considered) and
may still emit manual blocks.

Make `thresholds` optional on POST and accept an empty map in the
resolver, mirroring PATCH's behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chiappa 1 هفته پیش
والد
کامیت
137ce625e2
2فایلهای تغییر یافته به همراه29 افزوده شده و 8 حذف شده
  1. 10 8
      api/src/Application/Admin/PoliciesController.php
  2. 19 0
      api/tests/Integration/Admin/PoliciesControllerTest.php

+ 10 - 8
api/src/Application/Admin/PoliciesController.php

@@ -96,14 +96,16 @@ final class PoliciesController
         }
 
         $thresholds = [];
-        if (!array_key_exists('thresholds', $body) || !is_array($body['thresholds'])) {
-            $errors['thresholds'] = 'required, object {category_slug: number}';
-        } else {
-            $resolved = $this->resolveThresholds($body['thresholds']);
-            if (is_string($resolved)) {
-                $errors['thresholds'] = $resolved;
+        if (array_key_exists('thresholds', $body)) {
+            if (!is_array($body['thresholds'])) {
+                $errors['thresholds'] = 'must be object {category_slug: number}';
             } else {
-                $thresholds = $resolved;
+                $resolved = $this->resolveThresholds($body['thresholds']);
+                if (is_string($resolved)) {
+                    $errors['thresholds'] = $resolved;
+                } else {
+                    $thresholds = $resolved;
+                }
             }
         }
 
@@ -297,7 +299,7 @@ final class PoliciesController
     private function resolveThresholds(array $raw): array|string
     {
         if ($raw === []) {
-            return 'must contain at least one entry';
+            return [];
         }
         $idBySlug = [];
         foreach ($this->categories->listAll() as $cat) {

+ 19 - 0
api/tests/Integration/Admin/PoliciesControllerTest.php

@@ -84,6 +84,25 @@ final class PoliciesControllerTest extends AppTestCase
         self::assertCount(2, $body['thresholds']);
     }
 
+    public function testAdminCanCreatePolicyWithoutThresholds(): void
+    {
+        // The UI's create form deliberately omits the threshold matrix
+        // ("configure on edit page after creation"). A policy with zero
+        // thresholds is valid per SPEC §4 (absent row = category not
+        // considered) and may still emit manual blocks.
+        $token = $this->createToken(TokenKind::Admin, role: Role::Admin);
+        $response = $this->request(
+            'POST',
+            '/api/v1/admin/policies',
+            ['Authorization' => 'Bearer ' . $token, 'Content-Type' => 'application/json'],
+            json_encode(['name' => 'empty', 'include_manual_blocks' => true]) ?: null,
+        );
+        self::assertSame(201, $response->getStatusCode());
+        $body = $this->decode($response);
+        self::assertSame('empty', $body['name']);
+        self::assertSame([], $body['thresholds']);
+    }
+
     public function testCreateRejectsDuplicateName(): void
     {
         $token = $this->createToken(TokenKind::Admin, role: Role::Admin);