Skip to content

Commit 998a6bb

Browse files
author
zengfr
committed
ci+test: 升级 actions v5/v6 消除 Node 20 warning + contracts/runtime 测试
- actions/checkout v4→v5, setup-go v5→v6, setup-node v4→v5 - pkg/contracts: CrossCheck 全路径测试(matchPath/methodExists/空契约) - pkg/runtime: Backend 常量验证 + Runtime 接口编译期检查 - contracts 100% 覆盖率, runtime 接口契约测试
1 parent a26b3e4 commit 998a6bb

3 files changed

Lines changed: 166 additions & 7 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ jobs:
1010
go:
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v4
14-
- uses: actions/setup-go@v5
13+
- uses: actions/checkout@v5
14+
- uses: actions/setup-go@v6
1515
with:
1616
go-version: "1.25"
1717
- name: Download dependencies
@@ -32,8 +32,8 @@ jobs:
3232
runs-on: ubuntu-latest
3333
needs: go
3434
steps:
35-
- uses: actions/checkout@v4
36-
- uses: actions/setup-node@v4
35+
- uses: actions/checkout@v5
36+
- uses: actions/setup-node@v5
3737
with:
3838
node-version: "20"
3939
- name: Install dependencies
@@ -57,11 +57,11 @@ jobs:
5757
runs-on: ubuntu-latest
5858
needs: go
5959
steps:
60-
- uses: actions/checkout@v4
61-
- uses: actions/setup-go@v5
60+
- uses: actions/checkout@v5
61+
- uses: actions/setup-go@v6
6262
with:
6363
go-version: "1.25"
6464
- name: Build coordinator
6565
run: go build -o aicodingagentteam ./cmd/aicodingagentteam
6666
- name: Dogfood quality gate
67-
run: ./aicodingagentteam verify
67+
run: ./aicodingagentteam verify

‎pkg/contracts/contracts_test.go‎

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package contracts
2+
3+
import "testing"
4+
5+
func TestCrossCheck_NoMismatches(t *testing.T) {
6+
c := &Contract{
7+
Paths: map[string]PathItem{
8+
"/api/users": {Get: &Operation{OperationID: "listUsers", Method: "GET", Path: "/api/users"}},
9+
},
10+
}
11+
calls := []CallSite{
12+
{File: "frontend.ts", Method: "GET", Path: "/api/users", Line: 10},
13+
}
14+
mismatches := CrossCheck(c, calls)
15+
if len(mismatches) != 0 {
16+
t.Errorf("expected 0 mismatches, got %d: %v", len(mismatches), mismatches)
17+
}
18+
}
19+
20+
func TestCrossCheck_PathNotInContract(t *testing.T) {
21+
c := &Contract{
22+
Paths: map[string]PathItem{
23+
"/api/users": {Get: &Operation{}},
24+
},
25+
}
26+
calls := []CallSite{
27+
{File: "app.ts", Method: "GET", Path: "/api/unknown", Line: 5},
28+
}
29+
mismatches := CrossCheck(c, calls)
30+
if len(mismatches) != 1 {
31+
t.Fatalf("expected 1 mismatch, got %d", len(mismatches))
32+
}
33+
if mismatches[0].Severity != "blocking" {
34+
t.Errorf("expected blocking severity, got %s", mismatches[0].Severity)
35+
}
36+
if mismatches[0].Call.Path != "/api/unknown" {
37+
t.Errorf("unexpected path: %s", mismatches[0].Call.Path)
38+
}
39+
}
40+
41+
func TestCrossCheck_MethodNotAllowed(t *testing.T) {
42+
c := &Contract{
43+
Paths: map[string]PathItem{
44+
"/api/users": {Get: &Operation{OperationID: "listUsers", Method: "GET"}},
45+
},
46+
}
47+
calls := []CallSite{
48+
{File: "app.ts", Method: "DELETE", Path: "/api/users", Line: 12},
49+
}
50+
mismatches := CrossCheck(c, calls)
51+
if len(mismatches) != 1 {
52+
t.Fatalf("expected 1 mismatch, got %d", len(mismatches))
53+
}
54+
if mismatches[0].Severity != "blocking" {
55+
t.Errorf("expected blocking, got %s", mismatches[0].Severity)
56+
}
57+
}
58+
59+
func TestCrossCheck_AllMethods(t *testing.T) {
60+
c := &Contract{
61+
Paths: map[string]PathItem{
62+
"/api/r": {
63+
Get: &Operation{Method: "GET"},
64+
Post: &Operation{Method: "POST"},
65+
Put: &Operation{Method: "PUT"},
66+
Delete: &Operation{Method: "DELETE"},
67+
Patch: &Operation{Method: "PATCH"},
68+
},
69+
},
70+
}
71+
for _, m := range []string{"GET", "POST", "PUT", "DELETE", "PATCH"} {
72+
calls := []CallSite{{Method: m, Path: "/api/r"}}
73+
if mm := CrossCheck(c, calls); len(mm) != 0 {
74+
t.Errorf("method %s should be allowed, got mismatches: %v", m, mm)
75+
}
76+
}
77+
}
78+
79+
func TestMethodExists_UnknownMethod(t *testing.T) {
80+
item := PathItem{Get: &Operation{}}
81+
if methodExists(item, "HEAD") {
82+
t.Error("HEAD should not be recognized")
83+
}
84+
}
85+
86+
func TestCrossCheck_EmptyContract(t *testing.T) {
87+
c := &Contract{Paths: map[string]PathItem{}}
88+
calls := []CallSite{{File: "f.ts", Method: "GET", Path: "/anything"}}
89+
mismatches := CrossCheck(c, calls)
90+
if len(mismatches) != 1 {
91+
t.Fatalf("expected 1 mismatch for empty contract, got %d", len(mismatches))
92+
}
93+
}

‎pkg/runtime/runtime_test.go‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package runtime
2+
3+
import (
4+
"context"
5+
"testing"
6+
)
7+
8+
func TestBackendConstants(t *testing.T) {
9+
backends := map[Backend]string{
10+
BackendClaudeCode: "claude-code",
11+
BackendCodex: "codex",
12+
BackendOpenCode: "opencode",
13+
BackendDSH: "deepseek-dsh",
14+
}
15+
for b, want := range backends {
16+
if string(b) != want {
17+
t.Errorf("backend %q expected %q, got %q", b, want, string(b))
18+
}
19+
}
20+
}
21+
22+
func TestEventTypes(t *testing.T) {
23+
cases := []struct {
24+
got EventType
25+
want string
26+
}{
27+
{EventStart, "start"},
28+
{EventMessage, "message"},
29+
{EventToolCall, "tool_call"},
30+
{EventDone, "done"},
31+
{EventError, "error"},
32+
}
33+
for _, c := range cases {
34+
if string(c.got) != c.want {
35+
t.Errorf("event type %q expected %q", c.got, c.want)
36+
}
37+
}
38+
}
39+
40+
// TestRuntimeInterfaceCompiles ensures the Runtime interface is satisfiable
41+
// and its method signatures remain stable (compile-time check).
42+
type stubRuntime struct{}
43+
44+
func (stubRuntime) StartSession(ctx context.Context, opts SessionOpts) (SessionID, error) {
45+
return "", nil
46+
}
47+
func (stubRuntime) DestroySession(ctx context.Context, id SessionID) error { return nil }
48+
func (stubRuntime) SendTask(ctx context.Context, id SessionID, task TaskPayload) (<-chan Event, error) {
49+
return nil, nil
50+
}
51+
func (stubRuntime) Capabilities() HostCapabilities { return HostCapabilities{} }
52+
func (stubRuntime) ModelInfo() ModelInfo { return ModelInfo{} }
53+
func (stubRuntime) Pause(ctx context.Context, id SessionID) error { return nil }
54+
func (stubRuntime) Resume(ctx context.Context, id SessionID) error { return nil }
55+
func (stubRuntime) AuthStatus(ctx context.Context, id SessionID) (AuthStatus, error) {
56+
return AuthStatus{}, nil
57+
}
58+
59+
var _ Runtime = stubRuntime{}
60+
61+
func TestRuntimeInterfaceImplemented(t *testing.T) {
62+
var r Runtime = stubRuntime{}
63+
if _, err := r.AuthStatus(context.TODO(), ""); err != nil {
64+
t.Errorf("unexpected error: %v", err)
65+
}
66+
}

0 commit comments

Comments
 (0)