fix: harden skill template contracts and scaffolding
All checks were successful
技能自动化发布 / release (push) Successful in 4s
All checks were successful
技能自动化发布 / release (push) Successful in 4s
Align scaffold slug replacement, Action manifest strict boundaries, metadata prune API, field permission validation, and task_logs readonly semantics with documented template contracts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
"""数据管理展示元数据:幂等初始化、中文名称、PRAGMA 字段顺序、校验规则。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
import unittest
|
||||
|
||||
@@ -35,22 +36,39 @@ class TestDisplayMetadata(unittest.TestCase):
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"SELECT display_name FROM _jiangchang_tables WHERE table_name = 'task_logs'"
|
||||
"SELECT display_name, readonly FROM _jiangchang_tables WHERE table_name = 'task_logs'"
|
||||
)
|
||||
self.assertEqual(cur.fetchone()[0], "任务日志")
|
||||
display_name, readonly = cur.fetchone()
|
||||
self.assertEqual(display_name, "任务日志")
|
||||
self.assertEqual(int(readonly), 1)
|
||||
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT column_name, display_name
|
||||
SELECT column_name, display_name, editable
|
||||
FROM _jiangchang_columns
|
||||
WHERE table_name = 'task_logs'
|
||||
ORDER BY column_name
|
||||
"""
|
||||
)
|
||||
by_name = dict(cur.fetchall())
|
||||
self.assertEqual(by_name["status"], "状态")
|
||||
self.assertEqual(by_name["error_msg"], "错误信息")
|
||||
self.assertEqual(by_name["created_at"], "创建时间")
|
||||
by_name = {row[0]: (row[1], row[2]) for row in cur.fetchall()}
|
||||
self.assertEqual(by_name["status"][0], "状态")
|
||||
self.assertEqual(int(by_name["status"][1]), 0)
|
||||
self.assertEqual(by_name["error_msg"][0], "错误信息")
|
||||
self.assertEqual(int(by_name["error_msg"][1]), 0)
|
||||
self.assertEqual(by_name["created_at"][0], "创建时间")
|
||||
self.assertEqual(int(by_name["created_at"][1]), 0)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_task_logs_not_manual_create_in_host_ui(self) -> None:
|
||||
with IsolatedDataRoot():
|
||||
from db.connection import get_conn, init_db
|
||||
from db.display_metadata import table_allows_manual_create
|
||||
|
||||
init_db()
|
||||
conn = get_conn()
|
||||
try:
|
||||
self.assertFalse(table_allows_manual_create(conn.cursor(), "task_logs"))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@@ -342,6 +360,153 @@ metadata:
|
||||
)
|
||||
self.assertTrue(any("id" in item for item in warnings))
|
||||
|
||||
def test_generated_column_editable_must_be_zero(self) -> None:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
try:
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE gen_demo (
|
||||
id INTEGER PRIMARY KEY,
|
||||
base_value INTEGER NOT NULL,
|
||||
generated_value INTEGER GENERATED ALWAYS AS (base_value * 2) STORED
|
||||
);
|
||||
"""
|
||||
)
|
||||
from db.display_metadata import init_display_metadata_tables, upsert_column_metadata
|
||||
from db.display_metadata_validator import validate_column_permissions
|
||||
|
||||
cur = conn.cursor()
|
||||
init_display_metadata_tables(cur)
|
||||
upsert_column_metadata(
|
||||
cur,
|
||||
table_name="gen_demo",
|
||||
column_name="generated_value",
|
||||
display_name="生成值",
|
||||
editable=1,
|
||||
)
|
||||
conn.commit()
|
||||
errors, _ = validate_column_permissions(cur, "gen_demo")
|
||||
self.assertTrue(any("generated" in err for err in errors))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_blob_column_editable_must_be_zero(self) -> None:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
try:
|
||||
conn.execute(
|
||||
"CREATE TABLE blob_demo (id INTEGER PRIMARY KEY, payload BLOB NOT NULL)"
|
||||
)
|
||||
from db.display_metadata import init_display_metadata_tables, upsert_column_metadata
|
||||
from db.display_metadata_validator import validate_column_permissions
|
||||
|
||||
cur = conn.cursor()
|
||||
init_display_metadata_tables(cur)
|
||||
upsert_column_metadata(
|
||||
cur,
|
||||
table_name="blob_demo",
|
||||
column_name="payload",
|
||||
display_name="载荷",
|
||||
editable=1,
|
||||
)
|
||||
conn.commit()
|
||||
errors, _ = validate_column_permissions(cur, "blob_demo")
|
||||
self.assertTrue(any("BLOB" in err for err in errors))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_primary_key_editable_must_be_zero(self) -> None:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
try:
|
||||
conn.execute("CREATE TABLE pk_demo (id INTEGER PRIMARY KEY, title TEXT)")
|
||||
from db.display_metadata import init_display_metadata_tables, upsert_column_metadata
|
||||
from db.display_metadata_validator import validate_column_permissions
|
||||
|
||||
cur = conn.cursor()
|
||||
init_display_metadata_tables(cur)
|
||||
upsert_column_metadata(
|
||||
cur,
|
||||
table_name="pk_demo",
|
||||
column_name="id",
|
||||
display_name="编号",
|
||||
editable=1,
|
||||
)
|
||||
conn.commit()
|
||||
errors, _ = validate_column_permissions(cur, "pk_demo")
|
||||
self.assertTrue(any("主键" in err for err in errors))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_readonly_table_with_editable_fields_warns(self) -> None:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
try:
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE warn_demo (id INTEGER PRIMARY KEY, title TEXT);
|
||||
"""
|
||||
)
|
||||
from db.display_metadata import (
|
||||
init_display_metadata_tables,
|
||||
upsert_column_metadata,
|
||||
upsert_table_metadata,
|
||||
)
|
||||
from db.display_metadata_validator import validate_column_permissions
|
||||
|
||||
cur = conn.cursor()
|
||||
init_display_metadata_tables(cur)
|
||||
upsert_table_metadata(cur, table_name="warn_demo", display_name="警告演示", readonly=1)
|
||||
upsert_column_metadata(
|
||||
cur,
|
||||
table_name="warn_demo",
|
||||
column_name="title",
|
||||
display_name="标题",
|
||||
editable=1,
|
||||
)
|
||||
conn.commit()
|
||||
_, warnings = validate_column_permissions(cur, "warn_demo")
|
||||
self.assertTrue(any("readonly=1" in item for item in warnings))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_writable_table_without_editable_fields_warns(self) -> None:
|
||||
conn = sqlite3.connect(":memory:")
|
||||
try:
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE warn_demo2 (id INTEGER PRIMARY KEY, title TEXT);
|
||||
"""
|
||||
)
|
||||
from db.display_metadata import (
|
||||
init_display_metadata_tables,
|
||||
upsert_column_metadata,
|
||||
upsert_table_metadata,
|
||||
)
|
||||
from db.display_metadata_validator import validate_column_permissions
|
||||
|
||||
cur = conn.cursor()
|
||||
init_display_metadata_tables(cur)
|
||||
upsert_table_metadata(cur, table_name="warn_demo2", display_name="警告演示二", readonly=0)
|
||||
upsert_column_metadata(
|
||||
cur,
|
||||
table_name="warn_demo2",
|
||||
column_name="title",
|
||||
display_name="标题",
|
||||
editable=0,
|
||||
)
|
||||
conn.commit()
|
||||
_, warnings = validate_column_permissions(cur, "warn_demo2")
|
||||
self.assertTrue(any("readonly=0" in item and "editable=0" in item for item in warnings))
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def test_schema_md_documents_task_logs_readonly_semantics(self) -> None:
|
||||
path = os.path.join(get_skill_root(), "references", "SCHEMA.md")
|
||||
with open(path, encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
self.assertIn("readonly = 1", text)
|
||||
self.assertIn("task_logs", text)
|
||||
self.assertIn("seed_column_metadata_batch", text)
|
||||
self.assertIn("valid_column_names", text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user