{
  "test_date": "2026-06-22 19:42:53",
  "module": "学校管理 (School Management)",
  "version": "v1",
  "base_url": "http://localhost:3000",
  "covered_submodules": [
    "schools",
    "departments",
    "academic-year"
  ],
  "summary": {
    "total": 15,
    "passed": 15,
    "failed": 0,
    "warnings": 0
  },
  "roles": {
    "admin": {
      "role": "admin",
      "login_success": true,
      "access_tests": [
        {
          "route_key": "schools_list",
          "url": "http://localhost:3000/admin/school/schools",
          "expected_access": true,
          "http_status": 200,
          "final_url": "http://localhost:3000/admin/school/schools",
          "status": "passed",
          "errors": [],
          "warnings": [],
          "checks": [
            {
              "name": "页面正常加载",
              "passed": true
            }
          ]
        },
        {
          "route_key": "departments_list",
          "url": "http://localhost:3000/admin/school/departments",
          "expected_access": true,
          "http_status": 200,
          "final_url": "http://localhost:3000/admin/school/departments",
          "status": "passed",
          "errors": [],
          "warnings": [],
          "checks": [
            {
              "name": "页面正常加载",
              "passed": true
            }
          ]
        },
        {
          "route_key": "academic_year_list",
          "url": "http://localhost:3000/admin/school/academic-year",
          "expected_access": true,
          "http_status": 200,
          "final_url": "http://localhost:3000/admin/school/academic-year",
          "status": "passed",
          "errors": [],
          "warnings": [],
          "checks": [
            {
              "name": "页面正常加载",
              "passed": true
            }
          ]
        }
      ],
      "crud_tests": [
        {
          "feature": "学校 CRUD",
          "role": "admin",
          "operations": [
            {
              "name": "列表加载",
              "passed": true,
              "detail": "列表/空状态正常显示"
            },
            {
              "name": "打开创建对话框",
              "passed": true,
              "detail": "创建对话框已打开"
            },
            {
              "name": "编辑入口存在",
              "passed": true,
              "detail": "无数据行，跳过编辑入口检查"
            },
            {
              "name": "删除入口存在",
              "passed": true,
              "detail": "无数据行，跳过删除入口检查"
            }
          ],
          "status": "passed",
          "errors": []
        },
        {
          "feature": "院系 CRUD",
          "role": "admin",
          "operations": [
            {
              "name": "列表加载",
              "passed": true,
              "detail": "列表/空状态正常显示"
            },
            {
              "name": "打开创建对话框",
              "passed": true,
              "detail": "创建对话框已打开"
            },
            {
              "name": "编辑入口存在",
              "passed": true,
              "detail": "无数据行，跳过"
            },
            {
              "name": "删除入口存在",
              "passed": true,
              "detail": "无数据行，跳过"
            }
          ],
          "status": "passed",
          "errors": []
        },
        {
          "feature": "学年 CRUD",
          "role": "admin",
          "operations": [
            {
              "name": "列表加载",
              "passed": true,
              "detail": "列表/空状态正常显示"
            },
            {
              "name": "打开创建对话框",
              "passed": true,
              "detail": "创建对话框已打开"
            },
            {
              "name": "编辑入口存在",
              "passed": true,
              "detail": "无数据行，跳过"
            },
            {
              "name": "删除入口存在",
              "passed": true,
              "detail": "无数据行，跳过"
            }
          ],
          "status": "passed",
          "errors": []
        }
      ],
      "errors": [],
      "warnings": [
        "控制台错误: A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n- A server/client bran",
        "控制台错误: A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n- A server/client bran"
      ]
    },
    "teacher": {
      "role": "teacher",
      "login_success": true,
      "access_tests": [
        {
          "route_key": "schools_list",
          "url": "http://localhost:3000/admin/school/schools",
          "expected_access": false,
          "http_status": 200,
          "final_url": "http://localhost:3000/teacher/dashboard?from=%2Fadmin%2Fschool%2Fschools&reason=forbidden",
          "status": "passed",
          "errors": [],
          "warnings": [],
          "checks": [
            {
              "name": "权限拒绝重定向",
              "passed": true,
              "detail": "正确重定向到 http://localhost:3000/teacher/dashboard?from=%2Fadmin%2Fschool%2Fschools&reason=forbidden"
            }
          ]
        },
        {
          "route_key": "departments_list",
          "url": "http://localhost:3000/admin/school/departments",
          "expected_access": false,
          "http_status": 200,
          "final_url": "http://localhost:3000/teacher/dashboard?from=%2Fadmin%2Fschool%2Fdepartments&reason=forbidden",
          "status": "passed",
          "errors": [],
          "warnings": [],
          "checks": [
            {
              "name": "权限拒绝重定向",
              "passed": true,
              "detail": "正确重定向到 http://localhost:3000/teacher/dashboard?from=%2Fadmin%2Fschool%2Fdepartments&reason=forbidden"
            }
          ]
        },
        {
          "route_key": "academic_year_list",
          "url": "http://localhost:3000/admin/school/academic-year",
          "expected_access": false,
          "http_status": 200,
          "final_url": "http://localhost:3000/teacher/dashboard?from=%2Fadmin%2Fschool%2Facademic-year&reason=forbidden",
          "status": "passed",
          "errors": [],
          "warnings": [],
          "checks": [
            {
              "name": "权限拒绝重定向",
              "passed": true,
              "detail": "正确重定向到 http://localhost:3000/teacher/dashboard?from=%2Fadmin%2Fschool%2Facademic-year&reason=forbidden"
            }
          ]
        }
      ],
      "crud_tests": [
        {
          "feature": "学校 CRUD",
          "role": "teacher",
          "operations": [],
          "status": "skipped",
          "errors": [
            "teacher 无 SCHOOL_MANAGE 权限，跳过 CRUD 测试"
          ]
        },
        {
          "feature": "院系 CRUD",
          "role": "teacher",
          "operations": [],
          "status": "skipped",
          "errors": [
            "teacher 无 SCHOOL_MANAGE 权限，跳过 CRUD 测试"
          ]
        },
        {
          "feature": "学年 CRUD",
          "role": "teacher",
          "operations": [],
          "status": "skipped",
          "errors": [
            "teacher 无 SCHOOL_MANAGE 权限，跳过 CRUD 测试"
          ]
        }
      ],
      "errors": [],
      "warnings": []
    },
    "student": {
      "role": "student",
      "login_success": true,
      "access_tests": [
        {
          "route_key": "schools_list",
          "url": "http://localhost:3000/admin/school/schools",
          "expected_access": false,
          "http_status": 200,
          "final_url": "http://localhost:3000/student/dashboard?from=%2Fadmin%2Fschool%2Fschools&reason=forbidden",
          "status": "passed",
          "errors": [],
          "warnings": [],
          "checks": [
            {
              "name": "权限拒绝重定向",
              "passed": true,
              "detail": "正确重定向到 http://localhost:3000/student/dashboard?from=%2Fadmin%2Fschool%2Fschools&reason=forbidden"
            }
          ]
        },
        {
          "route_key": "departments_list",
          "url": "http://localhost:3000/admin/school/departments",
          "expected_access": false,
          "http_status": 200,
          "final_url": "http://localhost:3000/student/dashboard?from=%2Fadmin%2Fschool%2Fdepartments&reason=forbidden",
          "status": "passed",
          "errors": [],
          "warnings": [],
          "checks": [
            {
              "name": "权限拒绝重定向",
              "passed": true,
              "detail": "正确重定向到 http://localhost:3000/student/dashboard?from=%2Fadmin%2Fschool%2Fdepartments&reason=forbidden"
            }
          ]
        },
        {
          "route_key": "academic_year_list",
          "url": "http://localhost:3000/admin/school/academic-year",
          "expected_access": false,
          "http_status": 200,
          "final_url": "http://localhost:3000/student/dashboard?from=%2Fadmin%2Fschool%2Facademic-year&reason=forbidden",
          "status": "passed",
          "errors": [],
          "warnings": [],
          "checks": [
            {
              "name": "权限拒绝重定向",
              "passed": true,
              "detail": "正确重定向到 http://localhost:3000/student/dashboard?from=%2Fadmin%2Fschool%2Facademic-year&reason=forbidden"
            }
          ]
        }
      ],
      "crud_tests": [
        {
          "feature": "学校 CRUD",
          "role": "student",
          "operations": [],
          "status": "skipped",
          "errors": [
            "student 无 SCHOOL_MANAGE 权限，跳过 CRUD 测试"
          ]
        },
        {
          "feature": "院系 CRUD",
          "role": "student",
          "operations": [],
          "status": "skipped",
          "errors": [
            "student 无 SCHOOL_MANAGE 权限，跳过 CRUD 测试"
          ]
        },
        {
          "feature": "学年 CRUD",
          "role": "student",
          "operations": [],
          "status": "skipped",
          "errors": [
            "student 无 SCHOOL_MANAGE 权限，跳过 CRUD 测试"
          ]
        }
      ],
      "errors": [],
      "warnings": []
    },
    "parent": {
      "role": "parent",
      "login_success": true,
      "access_tests": [
        {
          "route_key": "schools_list",
          "url": "http://localhost:3000/admin/school/schools",
          "expected_access": false,
          "http_status": 200,
          "final_url": "http://localhost:3000/parent/dashboard?from=%2Fadmin%2Fschool%2Fschools&reason=forbidden",
          "status": "passed",
          "errors": [],
          "warnings": [],
          "checks": [
            {
              "name": "权限拒绝重定向",
              "passed": true,
              "detail": "正确重定向到 http://localhost:3000/parent/dashboard?from=%2Fadmin%2Fschool%2Fschools&reason=forbidden"
            }
          ]
        },
        {
          "route_key": "departments_list",
          "url": "http://localhost:3000/admin/school/departments",
          "expected_access": false,
          "http_status": 200,
          "final_url": "http://localhost:3000/parent/dashboard?from=%2Fadmin%2Fschool%2Fdepartments&reason=forbidden",
          "status": "passed",
          "errors": [],
          "warnings": [],
          "checks": [
            {
              "name": "权限拒绝重定向",
              "passed": true,
              "detail": "正确重定向到 http://localhost:3000/parent/dashboard?from=%2Fadmin%2Fschool%2Fdepartments&reason=forbidden"
            }
          ]
        },
        {
          "route_key": "academic_year_list",
          "url": "http://localhost:3000/admin/school/academic-year",
          "expected_access": false,
          "http_status": 200,
          "final_url": "http://localhost:3000/parent/dashboard?from=%2Fadmin%2Fschool%2Facademic-year&reason=forbidden",
          "status": "passed",
          "errors": [],
          "warnings": [],
          "checks": [
            {
              "name": "权限拒绝重定向",
              "passed": true,
              "detail": "正确重定向到 http://localhost:3000/parent/dashboard?from=%2Fadmin%2Fschool%2Facademic-year&reason=forbidden"
            }
          ]
        }
      ],
      "crud_tests": [
        {
          "feature": "学校 CRUD",
          "role": "parent",
          "operations": [],
          "status": "skipped",
          "errors": [
            "parent 无 SCHOOL_MANAGE 权限，跳过 CRUD 测试"
          ]
        },
        {
          "feature": "院系 CRUD",
          "role": "parent",
          "operations": [],
          "status": "skipped",
          "errors": [
            "parent 无 SCHOOL_MANAGE 权限，跳过 CRUD 测试"
          ]
        },
        {
          "feature": "学年 CRUD",
          "role": "parent",
          "operations": [],
          "status": "skipped",
          "errors": [
            "parent 无 SCHOOL_MANAGE 权限，跳过 CRUD 测试"
          ]
        }
      ],
      "errors": [],
      "warnings": []
    }
  },
  "console_errors_global": [
    {
      "role": "admin",
      "error": "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n- A server/client branch `if (typeof window !== 'undefined')`.\n- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n- Date formatting in a user's locale which doesn't match the server.\n- External changing data without sending a snapshot of it along with the HTML.\n- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n%s%s https://react.dev/link/hydration-mismatch \n\n  ...\n    <MenuProvider scope={{Menu:[...], ...}} open={false} onOpenChange={function useCallbackRef.useMemo} content={null} ...>\n      <MenuProvider scope={{Menu:[...], ...}} onClose={function Menu.useCallback} isUsingKeyboardRef={{current:false}} ...>\n        <DropdownMenuTrigger asChild={true}>\n          <DropdownMenuTrigger data-slot=\"dropdown-m...\" asChild={true}>\n            <MenuAnchor asChild={true} __scopeMenu={{Menu:[...], ...}}>\n              <PopperAnchor __scopePopper={{Menu:[...], ...}} asChild={true} ref={null}>\n                <Primitive.div asChild={true} ref={function}>\n                  <Primitive.div.Slot ref={function}>\n                    <Primitive.div.SlotClone ref={function}>\n                      <Primitive.button type=\"button\" id=\"radix-_R_2...\" aria-haspopup=\"menu\" aria-expanded={false} ...>\n                        <Primitive.button.Slot type=\"button\" id=\"radix-_R_2...\" aria-haspopup=\"menu\" ...>\n                          <Primitive.button.SlotClone type=\"button\" id=\"radix-_R_2...\" aria-haspopup=\"menu\" ...>\n                            <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\" disabled={false} type=\"button\" ...>\n                              <button\n                                data-slot=\"dropdown-menu-trigger\"\n                                className={\"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-m...\"}\n                                disabled={false}\n                                type=\"button\"\n+                               id=\"radix-_R_2d59bn5ritpevqbn6lb_\"\n-                               id=\"radix-_R_9kl5esnebn9evqbn6lb_\"\n                                aria-haspopup=\"menu\"\n                                aria-expanded={false}\n                                aria-controls={undefined}\n                                data-state=\"closed\"\n                                data-disabled={undefined}\n                                onPointerDown={function handleEvent}\n                                onKeyDown={function handleEvent}\n                                ref={function}\n                              >\n        ...\n"
    },
    {
      "role": "admin",
      "error": "A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up. This can happen if a SSR-ed Client Component used:\n\n- A server/client branch `if (typeof window !== 'undefined')`.\n- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n- Date formatting in a user's locale which doesn't match the server.\n- External changing data without sending a snapshot of it along with the HTML.\n- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n\n%s%s https://react.dev/link/hydration-mismatch \n\n  ...\n    <MenuProvider scope={{Menu:[...], ...}} open={false} onOpenChange={function useCallbackRef.useMemo} content={null} ...>\n      <MenuProvider scope={{Menu:[...], ...}} onClose={function Menu.useCallback} isUsingKeyboardRef={{current:false}} ...>\n        <DropdownMenuTrigger asChild={true}>\n          <DropdownMenuTrigger data-slot=\"dropdown-m...\" asChild={true}>\n            <MenuAnchor asChild={true} __scopeMenu={{Menu:[...], ...}}>\n              <PopperAnchor __scopePopper={{Menu:[...], ...}} asChild={true} ref={null}>\n                <Primitive.div asChild={true} ref={function}>\n                  <Primitive.div.Slot ref={function}>\n                    <Primitive.div.SlotClone ref={function}>\n                      <Primitive.button type=\"button\" id=\"radix-_R_j...\" aria-haspopup=\"menu\" aria-expanded={false} ...>\n                        <Primitive.button.Slot type=\"button\" id=\"radix-_R_j...\" aria-haspopup=\"menu\" ...>\n                          <Primitive.button.SlotClone type=\"button\" id=\"radix-_R_j...\" aria-haspopup=\"menu\" ...>\n                            <Button variant=\"ghost\" size=\"icon\" className=\"h-8 w-8\" disabled={false} type=\"button\" ...>\n                              <button\n                                data-slot=\"dropdown-menu-trigger\"\n                                className={\"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-m...\"}\n                                disabled={false}\n                                type=\"button\"\n+                               id=\"radix-_R_j59bn5ritpevqbn6lb_\"\n-                               id=\"radix-_R_2cl5esnebn9evqbn6lb_\"\n                                aria-haspopup=\"menu\"\n                                aria-expanded={false}\n                                aria-controls={undefined}\n                                data-state=\"closed\"\n                                data-disabled={undefined}\n                                onPointerDown={function handleEvent}\n                                onKeyDown={function handleEvent}\n                                ref={function}\n                              >\n        ...\n"
    }
  ]
}