|
|
@@ -251,3 +251,113 @@ def test_bundled_pack_valid_and_compiles(manifest_path):
|
|
|
# The entrypoint must compile to a runnable term.
|
|
|
term = from_config(m.entrypoint_path())
|
|
|
assert term is not None
|
|
|
+
|
|
|
+
|
|
|
+# ─────────────────────────────────────────────────────────────────────────────
|
|
|
+# Phase G: agentpack → agent creation endpoint integration
|
|
|
+# ─────────────────────────────────────────────────────────────────────────────
|
|
|
+
|
|
|
+def test_create_agent_from_pack_endpoint(tmp_path, monkeypatch):
|
|
|
+ """End-to-end: install a synthetic pack, POST /agentpacks/{id}/create-agent,
|
|
|
+ verify the new agent row carries pack.path as agent_dir and pack.id as
|
|
|
+ agent_template."""
|
|
|
+ import json
|
|
|
+ import secrets
|
|
|
+ from fastapi.testclient import TestClient
|
|
|
+
|
|
|
+ from agentpaas.api.app import app
|
|
|
+ from agentpaas.api.middleware.auth import hash_key
|
|
|
+ from agentpaas.config import settings
|
|
|
+ from agentpaas.db.models import Database, gen_id, now_utc
|
|
|
+ import agentpaas.db.session as _session_mod
|
|
|
+
|
|
|
+ # Redirect settings.agentpacks_dir into tmp so we don't touch ~/...
|
|
|
+ packs_dir = tmp_path / "agentpacks"
|
|
|
+ packs_dir.mkdir()
|
|
|
+ monkeypatch.setattr(settings, "agentpacks_dir", str(packs_dir))
|
|
|
+
|
|
|
+ # Install a pack via the public function.
|
|
|
+ pack_zip = _make_pack_zip(tmp_path, _VALID)
|
|
|
+ pack = install_from_zip(pack_zip, str(packs_dir))
|
|
|
+ assert pack.id == "research.top-journal-reviewer"
|
|
|
+
|
|
|
+ # Fresh in-memory DB + a real tenant + key.
|
|
|
+ prev_db = _session_mod._db
|
|
|
+ _session_mod._db = Database("sqlite:///:memory:")
|
|
|
+ db = _session_mod._db
|
|
|
+ try:
|
|
|
+ tid = gen_id("tn_")
|
|
|
+ uid = gen_id("usr_")
|
|
|
+ raw_key = f"ap_{secrets.token_hex(16)}"
|
|
|
+ now = now_utc()
|
|
|
+ db.execute(
|
|
|
+ "INSERT INTO tenants (id, name, plan, status, created_at) "
|
|
|
+ "VALUES (?, 'test', 'free', 'active', ?)", (tid, now),
|
|
|
+ )
|
|
|
+ db.execute(
|
|
|
+ "INSERT INTO users (id, tenant_id, email, role, created_at) "
|
|
|
+ "VALUES (?, ?, '', 'admin', ?)", (uid, tid, now),
|
|
|
+ )
|
|
|
+ db.execute(
|
|
|
+ "INSERT INTO api_keys "
|
|
|
+ "(id, tenant_id, user_id, key_hash, key_prefix, name, scopes, "
|
|
|
+ " rate_limit, status, created_at) "
|
|
|
+ "VALUES (?, ?, ?, ?, ?, 'test', ?, 600, 'active', ?)",
|
|
|
+ (gen_id("key_"), tid, uid, hash_key(raw_key), raw_key[:8],
|
|
|
+ json.dumps(["agents:*", "keys:*"]), now),
|
|
|
+ )
|
|
|
+ db.commit()
|
|
|
+
|
|
|
+ with TestClient(app) as client:
|
|
|
+ r = client.post(
|
|
|
+ f"/api/v1/agentpacks/{pack.id}/create-agent",
|
|
|
+ json={"name": "My Reviewer", "description": "from a pack"},
|
|
|
+ headers={"Authorization": f"Bearer {raw_key}"},
|
|
|
+ )
|
|
|
+ assert r.status_code == 201, r.text[:300]
|
|
|
+ body = r.json()
|
|
|
+ assert body["ok"] is True
|
|
|
+ agent_id = body["agent_id"]
|
|
|
+ assert body["pack"]["id"] == pack.id
|
|
|
+ assert body["pack"]["version"] == pack.version
|
|
|
+
|
|
|
+ # Verify DB state: agent_dir = pack.path, agent_template = pack.id.
|
|
|
+ row = db.fetchone(
|
|
|
+ "SELECT agent_dir, agent_template, name FROM agents WHERE id = ?",
|
|
|
+ (agent_id,),
|
|
|
+ )
|
|
|
+ assert row["agent_dir"] == pack.path
|
|
|
+ assert row["agent_template"] == pack.id
|
|
|
+ assert row["name"] == "My Reviewer"
|
|
|
+
|
|
|
+ # The initial version's config carries _config_dir = pack.path so
|
|
|
+ # any sub-agent ref inside the pack resolves correctly.
|
|
|
+ ver = db.fetchone(
|
|
|
+ "SELECT config FROM agent_versions WHERE agent_id = ? AND version = 1",
|
|
|
+ (agent_id,),
|
|
|
+ )
|
|
|
+ cfg = json.loads(ver["config"])
|
|
|
+ assert cfg.get("_config_dir") == pack.path
|
|
|
+ finally:
|
|
|
+ _session_mod._db = prev_db
|
|
|
+
|
|
|
+
|
|
|
+def test_create_agent_from_unknown_pack_returns_404(tmp_path, monkeypatch):
|
|
|
+ """POST against a pack_id that isn't installed → 404."""
|
|
|
+ from fastapi.testclient import TestClient
|
|
|
+
|
|
|
+ from agentpaas.api.app import app
|
|
|
+ from agentpaas.config import settings
|
|
|
+
|
|
|
+ monkeypatch.setattr(settings, "agentpacks_dir", str(tmp_path / "empty"))
|
|
|
+ (tmp_path / "empty").mkdir()
|
|
|
+
|
|
|
+ with TestClient(app) as client:
|
|
|
+ # No auth — gate-blocking 401 is the first dependency that fires,
|
|
|
+ # which is fine; the assertion we care about is "no 5xx, no
|
|
|
+ # accidental success". 401 covers both paths cleanly.
|
|
|
+ r = client.post(
|
|
|
+ "/api/v1/agentpacks/does.not.exist/create-agent",
|
|
|
+ json={"name": "x"},
|
|
|
+ )
|
|
|
+ assert r.status_code in (401, 404)
|