-
Notifications
You must be signed in to change notification settings - Fork 0
contract_en.md
Automatically validates handler behavior based on schema-first route metadata, ensuring AI-generated code conforms to declared contracts.
When AI generates a handler, how do you know it's correct? Contract Testing makes "schema as contract" -- both request and response body structures must conform to the Input/Output types declared in Schema():
Schema declares Input: CreateUserRequest{Name, Email}
Schema declares Output: UserResponse{ID, Name, Email}
|
Request {"name":"alice"} <- missing email -> Input validation fails
Response {"id":1,"name":"alice"} <- missing email -> Output validation fails
Contract Testing validates both Input (request body) and Output (response body):
-
Input validation: When
ExpectSchema: trueand the Schema defines anInputtype, it automatically validates whethertc.InputJSON conforms to the Input struct - Output validation: Validates whether the handler response JSON conforms to the Output struct (including required field checks)
import (
"testing"
"github.com/maoxiaoyue/hypgo/pkg/contract"
)
func TestCreateUser(t *testing.T) {
r := setupRouter() // Router with Schema routes registered
contract.Test(t, r, contract.TestCase{
Route: "POST /api/users",
Input: `{"name":"alice","email":"alice@test.com"}`,
ExpectStatus: 201,
ExpectSchema: true, // Validate response conforms to Output schema
})
}Test all schema-registered routes with a single line of code:
func TestAllRoutes(t *testing.T) {
r := setupRouter()
contract.TestAll(t, r) // Automatically generates tests for each schema route
}TestAll will:
- Get all registered schema routes from
schema.Global() - Auto-generate a minimal valid request body for each route
- Auto-resolve path parameters (
:id->1) - Validate status codes and response schema
No schema needed -- just verify the route exists and returns the correct status code:
func TestHealthEndpoint(t *testing.T) {
r := setupRouter()
contract.TestRoute(t, r, "GET", "/health", 200)
}type TestCase struct {
Route string // "METHOD /path", e.g. "POST /api/users"
Input string // JSON request body
Headers map[string]string // Custom request headers
Query map[string]string // URL query parameters
ExpectStatus int // Expected HTTP status code
ExpectSchema bool // Whether to validate response against Output schema
ExpectBody string // Exact match response body (optional)
}When ExpectSchema is true, contract will:
- Look up the route's schema from
schema.Global() - Get the
Outputtype (Go struct) - Attempt to deserialize the response body JSON into that struct
- Check that all required fields are present
// Schema declaration
r.Schema(schema.Route{
Method: "GET",
Path: "/api/users/:id",
Output: UserResponse{}, // Has 3 required fields: ID, Name, Email
}).Handle(getUserHandler)
// If handler returns {"id":1} -> fails (missing name, email)
// If handler returns {"id":1,"name":"a","email":"b"} -> passes| struct tag | Required? |
|---|---|
json:"name" |
Yes |
json:"bio,omitempty" |
No |
Bio *string \json:"bio"`` |
No (pointer) |
If ExpectStatus > 0, validates that the response status code matches.
If ExpectBody is not empty, trims whitespace and performs an exact match against the response body.
TestAll automatically distinguishes protocols:
| Protocol | Behavior |
|---|---|
REST (Protocol empty or "rest") |
Sends HTTP request, validates Input/Output and status code |
| gRPC / Bot / MCP / WebSocket / CLI | Validates schema definition completeness (Command non-empty, Summary non-empty, type names populated) |
// TestAll with mixed protocols
schema.RegisterGRPC("UserService/CreateUser", "Create user", req, resp)
schema.RegisterBot("/start", "Start the bot", nil, WelcomeMsg{})
contract.TestAll(t, router)
// → REST routes: full HTTP testing
// → gRPC routes: schema completeness validation
// → Bot routes: schema completeness validationFor REST routes, TestAll uses the following strategies to auto-generate test cases:
| Path | Resolved Result |
|---|---|
/api/users/:id |
/api/users/1 |
/api/users/:userId/posts/:postId |
/api/users/1/posts/1 |
/api/:slug |
/api/test-slug |
/api/:name |
/api/test |
/files/*filepath |
/files/test.txt |
| Condition | Inferred Status Code |
|---|---|
| Responses has explicit 2xx declaration | Uses the smallest 2xx |
| POST (no declaration) | 201 |
| DELETE (no declaration) | 204 |
| Others (no declaration) | 200 |
Body is auto-generated only for POST, PUT, PATCH. Fills reasonable default values based on the Input struct fields:
| Go Type | Generated Value |
|---|---|
string |
"test" |
int, int64
|
0 |
float64 |
0.0 |
bool |
false |
[]T |
[] |
map[K]V |
{} |
package api_test
import (
"testing"
"github.com/maoxiaoyue/hypgo/pkg/contract"
"github.com/maoxiaoyue/hypgo/pkg/router"
"github.com/maoxiaoyue/hypgo/pkg/schema"
)
type CreateUserReq struct {
Name string `json:"name"`
Email string `json:"email"`
}
type UserResp struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func setupRouter() *router.Router {
r := router.New()
r.Schema(schema.Route{
Method: "POST",
Path: "/api/users",
Summary: "Create user",
Input: CreateUserReq{},
Output: UserResp{},
Responses: map[int]schema.ResponseSchema{
201: {Description: "Created"},
},
}).Handle(createUserHandler)
r.Schema(schema.Route{
Method: "GET",
Path: "/api/users/:id",
Output: UserResp{},
}).Handle(getUserHandler)
r.GET("/health", healthHandler)
return r
}
// Manual test for a specific route
func TestCreateUser(t *testing.T) {
r := setupRouter()
contract.Test(t, r, contract.TestCase{
Route: "POST /api/users",
Input: `{"name":"alice","email":"alice@test.com"}`,
ExpectStatus: 201,
ExpectSchema: true,
})
}
// Test with query parameters
func TestSearchUsers(t *testing.T) {
r := setupRouter()
contract.Test(t, r, contract.TestCase{
Route: "GET /api/users",
Query: map[string]string{"page": "1", "limit": "10"},
ExpectStatus: 200,
})
}
// Test with custom headers
func TestAuthRequired(t *testing.T) {
r := setupRouter()
contract.Test(t, r, contract.TestCase{
Route: "GET /api/admin",
Headers: map[string]string{"Authorization": "Bearer test-token"},
ExpectStatus: 200,
})
}
// One-click test for all schema routes
func TestAllContracts(t *testing.T) {
r := setupRouter()
contract.TestAll(t, r)
}
// Simple route check
func TestHealth(t *testing.T) {
r := setupRouter()
contract.TestRoute(t, r, "GET", "/health", 200)
}pkg/contract/
├── contract.go Test(), TestAll(), TestRoute() core test functions
├── validate.go validateResponse(), validateRequest(), validateRequiredFields()
├── generate.go generateTestCase(), generateMinimalJSON(), resolvePath()
└── contract_test.go 24 unit tests
pkg/contract -> pkg/router (Router.ServeHTTP to execute requests)
-> pkg/schema (Global() to look up schema metadata)
-> net/http/httptest (simulate HTTP requests)
Schema-first Routes Contract Testing
+------------------+ +------------------+
| Route{ | | TestCase{ |
| Input: Req{} |----->| ExpectSchema: |
| Output: Resp{} | | true |
| Responses: ... | | } |
| } | | |
+------------------+ +------------------+
| |
+--- schema.Global() ----+
(shared Registry)
Routes not registered with Schema() cannot undergo schema validation, but can still use TestRoute() to check status codes.
由 台灣卯小月 用 ❤️ 製作 · MIT License And Wiki is written by Claude
設計文件
套件
- config — 設定
- context — 請求上下文
- router — 路由器
- server — 伺服器
- middleware — 中介層
- websocket — WebSocket
- hidb — 資料庫 ORM
- hidb/cassandra — Cassandra
- logger — 日誌
- json — JSON 處理
- grpc — gRPC
AI 協作工具鏈
- schema — Schema-first 路由
- manifest — 專案 Manifest
- contract — Contract Testing
- errors — Typed Error Catalog
- migrate — Migration Diff
- scaffold — 智慧 Scaffold
- airules — AI Rules
CLI 命令
- hyp 總覽
- hyp new
- hyp api
- hyp run
- hyp restart
- hyp generate
- hyp migrate
- hyp context
- hyp ai-rules
- hyp chkcomment
- hyp impact
- hyp docker
- hyp health
- hyp version
- hyp difflog
Design Docs
Packages
- config — Configuration
- context — Request Context
- router — Router
- server — Server
- middleware — Middleware
- websocket — WebSocket
- hidb — Database ORM
- hidb/cassandra - Cassandra 5.0
- logger — Logger
- json — JSON
- grpc — gRPC
AI Collaboration Toolchain
- schema — Schema-first Routing
- manifest — Project Manifest
- contract — Contract Testing
- errors — Typed Error Catalog
- migrate — Migration Diff
- scaffold — Smart Scaffold
- airules — AI Rules
CLI Commands