9704281bdd
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 19s
CI / build (pull_request) Successful in 19s
CI / security (pull_request) Successful in 36s
CI / typecheck (pull_request) Successful in 39s
CI / unit_tests (pull_request) Successful in 2m29s
CI / docker (pull_request) Successful in 41s
CI / integration_tests (pull_request) Successful in 3m14s
CI / coverage (pull_request) Successful in 4m26s
CI / benchmark-regression (pull_request) Successful in 30m9s
CI / lint (push) Successful in 14s
CI / build (push) Successful in 16s
CI / quality (push) Successful in 17s
CI / security (push) Successful in 33s
CI / typecheck (push) Successful in 34s
CI / benchmark-regression (push) Has been skipped
CI / unit_tests (push) Successful in 2m18s
CI / docker (push) Successful in 39s
CI / integration_tests (push) Successful in 3m7s
CI / coverage (push) Successful in 4m29s
CI / benchmark-publish (push) Has been cancelled
Wire SkillService to use SkillRepository for database persistence, fixing the bug where `agents skill add` stored skills only in an in-memory OrderedDict that was lost when the CLI process exited. Changes: - SkillService now accepts optional skill_repo and session_factory parameters. When provided, add_skill() persists to the database and the constructor pre-loads existing skills from DB rows. - _get_skill_service() in the CLI now creates a DB-backed service following the same engine/sessionmaker/repository pattern used by the tool CLI (tool.py). - _reset_skill_service() now installs a fresh in-memory SkillService (instead of setting None) to avoid DB side-effects during unit testing with parallel runners. - remove_skill() also persists the deletion to the database. Test coverage: - Behave BDD: features/skill_add_persist.feature (4 scenarios) - Robot Framework: robot/skill_add_persist.robot (3 smoke tests) - ASV benchmark: benchmarks/skill_add_persist_bench.py ISSUES CLOSED: #620
141 lines
4.2 KiB
Python
141 lines
4.2 KiB
Python
"""Helper script for Robot Framework skill add persistence smoke tests.
|
|
|
|
Usage:
|
|
python helper_skill_add_persist.py <command>
|
|
|
|
Commands:
|
|
persist-round-trip Add a skill, recreate service, verify visible
|
|
persist-multiple Add two skills, recreate service, verify both
|
|
persist-remove Add skill, remove, recreate service, verify gone
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
# Ensure src is on the path
|
|
src_dir = str(Path(__file__).resolve().parents[1] / "src")
|
|
if src_dir not in sys.path:
|
|
sys.path.insert(0, src_dir)
|
|
|
|
from sqlalchemy import create_engine, event # noqa: E402
|
|
from sqlalchemy.orm import Session, sessionmaker # noqa: E402
|
|
|
|
from cleveragents.application.services.skill_service import ( # noqa: E402
|
|
SkillService,
|
|
)
|
|
from cleveragents.infrastructure.database.models import Base # noqa: E402
|
|
from cleveragents.infrastructure.database.repositories import ( # noqa: E402
|
|
SkillRepository,
|
|
)
|
|
from cleveragents.skills.schema import SkillConfigSchema # noqa: E402
|
|
|
|
|
|
def _enable_fk(dbapi_conn: Any, _rec: Any) -> None:
|
|
cursor = dbapi_conn.cursor()
|
|
cursor.execute("PRAGMA foreign_keys=ON")
|
|
cursor.close()
|
|
|
|
|
|
def _setup() -> tuple[sessionmaker[Session], Any]:
|
|
engine = create_engine("sqlite:///:memory:", echo=False)
|
|
event.listen(engine, "connect", _enable_fk)
|
|
Base.metadata.create_all(engine)
|
|
factory: sessionmaker[Session] = sessionmaker(bind=engine)
|
|
return factory, engine
|
|
|
|
|
|
def _make_service(factory: Any) -> SkillService:
|
|
shared = factory()
|
|
|
|
def _sf() -> Session:
|
|
return shared
|
|
|
|
repo = SkillRepository(session_factory=_sf)
|
|
return SkillService(skill_repo=repo, session_factory=_sf)
|
|
|
|
|
|
def _make_config(name: str) -> SkillConfigSchema:
|
|
import os
|
|
import tempfile
|
|
|
|
yaml_text = (
|
|
f"name: {name}\n"
|
|
f'description: "{name} test"\n'
|
|
"tools:\n"
|
|
" - name: builtin/read_file\n"
|
|
)
|
|
fd, path = tempfile.mkstemp(suffix=".yaml")
|
|
with os.fdopen(fd, "w") as fh:
|
|
fh.write(yaml_text)
|
|
schema = SkillConfigSchema.from_yaml_file(path)
|
|
Path(path).unlink(missing_ok=True)
|
|
return schema
|
|
|
|
|
|
def cmd_persist_round_trip() -> None:
|
|
"""Add a skill, recreate service, verify visible."""
|
|
factory, _engine = _setup()
|
|
svc = _make_service(factory)
|
|
|
|
config = _make_config("local/robot-persist")
|
|
svc.add_skill(config, config_path="/tmp/robot.yaml")
|
|
|
|
# Create a fresh service from the same DB
|
|
svc2 = _make_service(factory)
|
|
skills = svc2.list_skills()
|
|
names = [s.name for s in skills]
|
|
assert "local/robot-persist" in names, f"Expected skill in {names}"
|
|
print("skill-add-persist-round-trip-ok")
|
|
|
|
|
|
def cmd_persist_multiple() -> None:
|
|
"""Add two skills, recreate service, verify both visible."""
|
|
factory, _engine = _setup()
|
|
svc = _make_service(factory)
|
|
|
|
svc.add_skill(_make_config("local/multi-a"), config_path="/tmp/a.yaml")
|
|
svc.add_skill(_make_config("local/multi-b"), config_path="/tmp/b.yaml")
|
|
|
|
svc2 = _make_service(factory)
|
|
skills = svc2.list_skills()
|
|
names = [s.name for s in skills]
|
|
assert "local/multi-a" in names, f"Expected multi-a in {names}"
|
|
assert "local/multi-b" in names, f"Expected multi-b in {names}"
|
|
assert len(names) == 2, f"Expected 2 skills, got {len(names)}"
|
|
print("skill-add-persist-multiple-ok")
|
|
|
|
|
|
def cmd_persist_remove() -> None:
|
|
"""Add skill, remove, recreate service, verify gone."""
|
|
factory, _engine = _setup()
|
|
svc = _make_service(factory)
|
|
|
|
svc.add_skill(_make_config("local/remove-me"), config_path="/tmp/rm.yaml")
|
|
svc.remove_skill("local/remove-me")
|
|
|
|
svc2 = _make_service(factory)
|
|
skills = svc2.list_skills()
|
|
names = [s.name for s in skills]
|
|
assert "local/remove-me" not in names, f"Skill should be gone but found in {names}"
|
|
print("skill-add-persist-remove-ok")
|
|
|
|
|
|
COMMANDS: dict[str, Any] = {
|
|
"persist-round-trip": cmd_persist_round_trip,
|
|
"persist-multiple": cmd_persist_multiple,
|
|
"persist-remove": cmd_persist_remove,
|
|
}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if len(sys.argv) < 2 or sys.argv[1] not in COMMANDS:
|
|
print(
|
|
f"Usage: {sys.argv[0]} <{'|'.join(COMMANDS)}>",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
COMMANDS[sys.argv[1]]()
|