test(contracts): complete P1 contract tests with schemas and policy

- Add config.get, skills.list WS contract tests
- Add /v1/providers HTTP contract test
- Add JSON schemas for ws_connect, ws_chat_send, http_chat_completions
- Document breaking change policy
This commit is contained in:
viettranx committed 2026-04-12 21:52:29 +07:00
1 parent cc21384ff4
commit 6e7e15cf4a
7 files changed
+211

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
# Contract Breaking Change Policy
## What is a Breaking Change?
A breaking change is any modification to the API response schema that could cause existing clients to fail:
| Change Type | Breaking? | Example |
|------------|-----------|---------|
| Remove required field | YES | `{id, name}` → `{id}` |
| Change field type | YES | `"count": 5` → `"count": "5"` |
| Rename field | YES | `user_id` → `userId` |
| Add new required field | YES | Client must send new field |
| Add optional field | NO | Backward compatible |
| Add new enum value | MAYBE | If client validates strictly |
## Contract Test Categories
### P0 - Critical (WS Core)
- `connect` - Session establishment
- `chat.send` - Message handling
### P1 - High (Data APIs)
- `agents.list`, `sessions.list`, `skills.list`
- `/v1/chat/completions` - OpenAI compatibility
- `/v1/agents` - Agent management
### P2 - Medium (Config/Admin)
- `config.get`, `config.apply`
- `/v1/providers`
## Breaking Change Process
1. **Detect**: Contract test fails in CI
2. **Evaluate**: Is this intentional or accidental?
3. **If Intentional**:
- Bump protocol version in `pkg/protocol/version.go`
- Update JSON schemas in `tests/contracts/schemas/`
- Update contract tests
- Document in CHANGELOG.md
4. **If Accidental**: Fix the regression
## Running Contract Tests
```bash
# Set environment variables
export CONTRACT_TEST_WS_URL="ws://localhost:8080/ws"
export CONTRACT_TEST_HTTP_URL="http://localhost:8080"
export CONTRACT_TEST_TOKEN="your-test-token"
# Run all contract tests
go test -tags integration ./tests/contracts/...
```
## Schema Files
JSON Schema definitions in `tests/contracts/schemas/`:
- `ws_connect.json` - WS connect response
- `ws_chat_send.json` - WS chat.send response
- `http_chat_completions.json` - OpenAI-compatible response
These schemas serve as documentation and can be used for automated validation.
+26
View File
@@ -0,0 +1,26 @@
//go:build integration
package http_api
import "testing"
// CONTRACT: /v1/providers response MUST include providers array.
func TestContract_HTTP_ProvidersList(t *testing.T) {
baseURL, token := getTestServer(t)
client := newHTTPClient(baseURL, token)
resp := client.get(t, "/v1/providers")
assertField(t, resp, "providers", "array")
providers, ok := resp["providers"].([]any)
if !ok || len(providers) == 0 {
t.Log("No providers - skipping field checks")
return
}
provider := providers[0].(map[string]any)
assertField(t, provider, "id", "string")
assertField(t, provider, "name", "string")
assertField(t, provider, "type", "string")
}
@@ -0,0 +1,40 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "POST /v1/chat/completions response (OpenAI-compatible)",
"type": "object",
"required": ["id", "object", "created", "model", "choices"],
"properties": {
"id": { "type": "string" },
"object": { "type": "string", "const": "chat.completion" },
"created": { "type": "number" },
"model": { "type": "string" },
"choices": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": ["index", "message", "finish_reason"],
"properties": {
"index": { "type": "number" },
"message": {
"type": "object",
"required": ["role", "content"],
"properties": {
"role": { "type": "string" },
"content": { "type": "string" }
}
},
"finish_reason": { "type": "string" }
}
}
},
"usage": {
"type": "object",
"properties": {
"prompt_tokens": { "type": "number" },
"completion_tokens": { "type": "number" },
"total_tokens": { "type": "number" }
}
}
}
}
+11
View File
@@ -0,0 +1,11 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "WS chat.send response",
"type": "object",
"required": ["message_id", "content", "role"],
"properties": {
"message_id": { "type": "string" },
"content": { "type": "string" },
"role": { "type": "string", "enum": ["assistant", "user", "system"] }
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "WS connect response",
"type": "object",
"required": ["protocol", "role", "user_id", "tenant_id", "is_owner", "server"],
"properties": {
"protocol": { "type": "number" },
"role": { "type": "string", "enum": ["admin", "operator", "viewer"] },
"user_id": { "type": "string" },
"tenant_id": { "type": "string" },
"is_owner": { "type": "boolean" },
"server": {
"type": "object",
"required": ["name", "version"],
"properties": {
"name": { "type": "string", "const": "goclaw" },
"version": { "type": "string" }
}
}
}
}
@@ -0,0 +1,19 @@
//go:build integration
package ws_methods
import "testing"
// CONTRACT: config.get response MUST include config, hash, path.
// Requires owner + master scope.
func TestContract_WS_ConfigGet(t *testing.T) {
wsURL, _ := getTestServer(t)
client, _ := connect(t, wsURL, nil)
resp := client.send(t, "config.get", map[string]any{})
// Required fields
assertField(t, resp, "config", "object")
assertField(t, resp, "hash", "string")
assertField(t, resp, "path", "string")
}
@@ -0,0 +1,33 @@
//go:build integration
package ws_methods
import "testing"
// CONTRACT: skills.list response MUST include skills array.
func TestContract_WS_SkillsList(t *testing.T) {
wsURL, _ := getTestServer(t)
client, _ := connect(t, wsURL, nil)
resp := client.send(t, "skills.list", map[string]any{})
// Response must have skills array
assertField(t, resp, "skills", "array")
// If skills exist, verify each has required fields
skills, ok := resp["skills"].([]any)
if !ok || len(skills) == 0 {
t.Log("No skills returned - skipping field checks")
return
}
skill, ok := skills[0].(map[string]any)
if !ok {
t.Error("CONTRACT VIOLATION: skills[0] is not an object")
return
}
// Required skill fields
assertField(t, skill, "name", "string")
assertField(t, skill, "description", "string")
}