Recently Written · git

ai-config

One canonical AI agent config (rules, skills, MCP servers) rendered into Claude Code, Codex, Copilot CLI and Antigravity by acsync

git clone https://github.com/equwal/ai-config

Log | Files | Refs


skills/memstate/scripts/test_skill.py (7426 bytes)

1 #!/usr/bin/env python3
2 """
3 End-to-end test suite for the Memstate AI Skill.
4 Tests all 7 scripts against the live API.
5 """
6 import json
7 import os
8 import subprocess
9 import sys
10 import time
11 
12 SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
13 PROJECT_ID = f"skill-test-{int(time.time())}"
14 
15 PASS = "\033[92m✅ PASS\033[0m"
16 FAIL = "\033[91m❌ FAIL\033[0m"
17 SKIP = "\033[93m⚠️  SKIP\033[0m"
18 
19 results = []
20 
21 def run_script(script, *args):
22     cmd = [sys.executable, os.path.join(SCRIPTS_DIR, script)] + list(args)
23     result = subprocess.run(cmd, capture_output=True, text=True)
24     return result.returncode, result.stdout, result.stderr
25 
26 def test(name, script, args, expect_key=None, expect_value=None):
27     code, stdout, stderr = run_script(script, *args)
28     passed = code == 0
29     detail = ""
30     if passed and expect_key:
31         try:
32             json_str = stdout
33             if "Polling for completion..." in stdout:
34                 json_str = stdout.split("Polling for completion...")[1].strip()
35             data = json.loads(json_str)
36             actual = data.get(expect_key)
37             if expect_value is not None and actual != expect_value:
38                 passed = False
39                 detail = f" (expected {expect_key}={expect_value!r}, got {actual!r})"
40             elif actual is None:
41                 passed = False
42                 detail = f" (key '{expect_key}' missing from response)"
43         except json.JSONDecodeError:
44             passed = False
45             detail = " (response was not valid JSON)"
46     status = PASS if passed else FAIL
47     print(f"{status} {name}{detail}")
48     if not passed:
49         if stderr:
50             print(f"       stderr: {stderr.strip()}")
51         if stdout:
52             print(f"       stdout: {stdout.strip()[:300]}")
53     results.append((name, passed))
54     return passed, stdout
55 
56 def main():
57     print(f"\n{'='*60}")
58     print(f"  Memstate AI Skill — End-to-End Test Suite")
59     print(f"  Project: {PROJECT_ID}")
60     print(f"{'='*60}\n")
61 
62     # 1. memstate_set — store a fact; the first write creates the project
63     ok, out = test(
64         "memstate_set: store a single fact",
65         "memstate_set.py",
66         ["--project", PROJECT_ID, "--new-project", "--keypath", "database.engine", "--value", "PostgreSQL", "--category", "decision"],
67         expect_key="action", expect_value="created"
68     )
69 
70     # 2. memstate_set — update the same keypath (should supersede)
71     ok, out = test(
72         "memstate_set: update same keypath (supersede)",
73         "memstate_set.py",
74         ["--project", PROJECT_ID, "--keypath", "database.engine", "--value", "PostgreSQL 16", "--category", "decision"],
75         expect_key="action", expect_value="superseded"
76     )
77     memory_id = None
78     if ok:
79         try:
80             memory_id = json.loads(out)["stored"]["id"]
81         except Exception:
82             pass
83 
84     # 3. memstate_set — store a second fact
85     test(
86         "memstate_set: store a second fact",
87         "memstate_set.py",
88         ["--project", PROJECT_ID, "--keypath", "auth.method", "--value", "JWT with httpOnly cookies", "--category", "decision"],
89         expect_key="action", expect_value="created"
90     )
91 
92     # 4. memstate_get — list all projects
93     test(
94         "memstate_get: list all projects",
95         "memstate_get.py",
96         ["--list-projects"],
97         expect_key="projects"
98     )
99 
100     # 5. memstate_get — project tree
101     test(
102         "memstate_get: project tree",
103         "memstate_get.py",
104         ["--project", PROJECT_ID],
105         expect_key="domains"
106     )
107 
108     # 6. memstate_get - subtree with content
109     test(
110         "memstate_get: subtree with --include-content",
111         "memstate_get.py",
112         ["--project", PROJECT_ID, "--keypath", "database", "--include-content"],
113         expect_key="memories"
114     )
115 
116     # 7. memstate_get — by memory_id
117     if memory_id:
118         test(
119             "memstate_get: fetch by memory_id",
120             "memstate_get.py",
121             ["--memory-id", str(memory_id)],
122             expect_key="id", expect_value=memory_id
123         )
124     else:
125         print(f"{SKIP} memstate_get: fetch by memory_id (no memory_id from previous step)")
126         results.append(("memstate_get: fetch by memory_id", None))
127 
128     # 8. memstate_search — semantic search
129     test(
130         "memstate_search: semantic search",
131         "memstate_search.py",
132         ["--project", PROJECT_ID, "--query", "what database does the project use"],
133         expect_key="results"
134     )
135 
136     # 9. memstate_history - by keypath
137     test(
138         "memstate_history: by keypath",
139         "memstate_history.py",
140         ["--project", PROJECT_ID, "--keypath", "database.engine"],
141         expect_key="versions"
142     )
143 
144     # 10. memstate_remember — markdown ingestion (async)
145     test(
146         "memstate_remember: markdown ingestion",
147         "memstate_remember.py",
148         ["--project", PROJECT_ID,
149          "--content", "## Architecture Summary\n- Backend: FastAPI\n- Database: PostgreSQL 16\n- Auth: JWT with httpOnly cookies\n- Deploy: Docker on AWS ECS",
150          "--source", "agent"],
151         expect_key="method", expect_value="headings"
152     )
153 
154     # 11. memstate_delete — soft-delete a keypath
155     test(
156         "memstate_delete: soft-delete a keypath",
157         "memstate_delete.py",
158         ["--project", PROJECT_ID, "--keypath", "auth.method"],
159         expect_key="deleted_count"
160     )
161 
162     # 12. memstate_delete_project — cleanup
163     test(
164         "memstate_delete_project: cleanup test project",
165         "memstate_delete_project.py",
166         ["--project", PROJECT_ID],
167         expect_key="project_id", expect_value=PROJECT_ID
168     )
169 
170     # 13. user scope — the daemon allows only the allowlisted shapes there.
171     user_kp = f"preferences.skill_test_{int(time.time())}"
172     test(
173         "memstate_set: --scope user accepts a preferences fact",
174         "memstate_set.py",
175         ["--scope", "user", "--keypath", user_kp, "--value", "terse answers"],
176         expect_key="action", expect_value="created"
177     )
178     code, stdout, stderr = run_script(
179         "memstate_set.py", "--scope", "user", "--keypath", "todo.skill_test", "--value", "nope")
180     rejected = code == 1 and "HTTP 400" in stderr and "allowed" in stderr
181     print(f"{PASS if rejected else FAIL} memstate_set: --scope user rejects a todo keypath")
182     if not rejected:
183         print(f"       code={code} stderr={stderr.strip()[:300]}")
184     results.append(("memstate_set: --scope user rejects a todo keypath", rejected))
185     test(
186         "memstate_get: --scope user tree carries user.host",
187         "memstate_get.py",
188         ["--scope", "user"],
189         expect_key="host"
190     )
191     test(
192         "memstate_get: project tree carries the user scope",
193         "memstate_get.py",
194         ["--project", "skill_test_other"],
195         expect_key="user"
196     )
197     test(
198         "memstate_delete: --scope user cleans up",
199         "memstate_delete.py",
200         ["--scope", "user", "--keypath", user_kp],
201         expect_key="deleted_count"
202     )
203 
204     # Summary
205     passed = sum(1 for _, r in results if r is True)
206     failed = sum(1 for _, r in results if r is False)
207     skipped = sum(1 for _, r in results if r is None)
208     total = len(results)
209 
210     print(f"\n{'='*60}")
211     print(f"  Results: {passed}/{total} passed | {failed} failed | {skipped} skipped")
212     print(f"{'='*60}\n")
213 
214     return 0 if failed == 0 else 1
215 
216 if __name__ == "__main__":
217     sys.exit(main())