A test suite with no dependencies beyond bash and the Python standard library, run by one command: scripts/test.sh (or: make test) scripts/test.sh --windows [host] adds the PowerShell suite over SSH - tests/static.sh: script syntax, JSON validity, VERSION against the widget manifest and CHANGELOG, providers.json against the kinds, probes and checks the code implements (and ids that would shadow a command), qmllint and shellcheck when installed. - tests/python: unit tests for cm-json.py - providers and the column contract bash depends on, the original three scaffolds byte for byte, every catalogue parser and the cache, session scan/dismiss/repair, rename, defaults, health, the cost guard. - tests/cli: the CLI in a sandbox with its own HOME, CM_ROOT, CLAUDE_CONFIG_DIR and a file-only vault, against fake Ollama, LM Studio, keyed-gateway and proxy servers - preflight, presets, models, doctor, a real switch, sessions, and the key helper under a rename race. - tests/windows: the same idea on a Windows host, with USERPROFILE pointed at a temp folder so the real install is never touched. Also: MIT licence, CHANGELOG.md reconstructed from history, .editorconfig and .gitattributes pinning LF everywhere (what the tree already is), and scripts/bump-version.sh, since the version lives in two files and drifted once before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
77 lines
3.2 KiB
Python
Executable File
77 lines
3.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Fake provider servers for the CLI and Windows tests, all on 127.0.0.1.
|
|
|
|
ollama GET /api/tags tagged names only, as the real server lists them
|
|
GET /api/ps qwen3-coder:latest loaded with a 4096-token context
|
|
POST /api/show a 262144-token maximum
|
|
lmstudio GET /api/v0/models
|
|
keyed GET /v1/models, only with `Bearer sk-real` or `x-api-key: sk-real`
|
|
proxy 404 for everything - a Messages-only proxy with no model list
|
|
|
|
Ports are chosen by the OS. The first line printed is a JSON map of kind to
|
|
port, which the harness reads before running anything.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import threading
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def log_message(self, *args):
|
|
pass
|
|
|
|
def send(self, code, obj):
|
|
body = json.dumps(obj).encode()
|
|
self.send_response(code)
|
|
self.send_header("content-type", "application/json")
|
|
self.send_header("content-length", str(len(body)))
|
|
self.end_headers()
|
|
self.wfile.write(body)
|
|
|
|
def do_GET(self):
|
|
kind, path = self.server.kind, self.path
|
|
if kind == "ollama" and path == "/api/tags":
|
|
return self.send(200, {"models": [
|
|
{"name": "qwen3-coder:latest", "model": "qwen3-coder:latest",
|
|
"details": {"parameter_size": "30.5B", "quantization_level": "Q4_K_M", "family": "qwen3moe"}},
|
|
{"name": "glm-4.7:cloud", "model": "glm-4.7:cloud", "details": {}}]})
|
|
if kind == "ollama" and path == "/api/ps":
|
|
return self.send(200, {"models": [
|
|
{"name": "qwen3-coder:latest", "model": "qwen3-coder:latest", "context_length": 4096}]})
|
|
if kind == "lmstudio" and path == "/api/v0/models":
|
|
return self.send(200, {"data": [
|
|
{"id": "qwen3-coder-30b", "state": "loaded", "max_context_length": 262144},
|
|
{"id": "gemma-small", "state": "not-loaded", "max_context_length": 8192}]})
|
|
if kind == "keyed" and path == "/v1/models":
|
|
if self.headers.get("authorization") == "Bearer sk-real" or self.headers.get("x-api-key") == "sk-real":
|
|
return self.send(200, {"data": [{"id": "gw/model-a"}, {"id": "gw/model-b"}]})
|
|
return self.send(401, {"error": "unauthorized"})
|
|
return self.send(404, {"error": "not found"})
|
|
|
|
def do_POST(self):
|
|
self.rfile.read(int(self.headers.get("content-length") or 0))
|
|
if self.server.kind == "ollama" and self.path == "/api/show":
|
|
return self.send(200, {"model_info": {"general.architecture": "qwen3moe",
|
|
"qwen3moe.context_length": 262144}})
|
|
return self.send(404, {"error": "not found"})
|
|
|
|
|
|
def main():
|
|
ports = {}
|
|
for kind in ("ollama", "lmstudio", "keyed", "proxy"):
|
|
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
|
|
server.kind = kind
|
|
ports[kind] = server.server_address[1]
|
|
threading.Thread(target=server.serve_forever, daemon=True).start()
|
|
print(json.dumps(ports), flush=True)
|
|
threading.Event().wait()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
main()
|
|
except KeyboardInterrupt:
|
|
sys.exit(0)
|