Tests, licence, and the tooling to keep the repo consistent

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>
This commit is contained in:
smoido
2026-09-15 01:42:38 +03:00
co-authored by Claude Opus 5
parent b5824c6611
commit 51e42823d1
22 changed files with 1437 additions and 0 deletions
+76
View File
@@ -0,0 +1,76 @@
#!/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)