Skip to content

Commit a39ce4e

Browse files
authored
Merge pull request #890 from chaitin/fix/mcp-sync-health-status
同步 MCP 工具时更新健康状态
2 parents c9ec73c + 1c880f3 commit a39ce4e

4 files changed

Lines changed: 162 additions & 0 deletions

File tree

backend/biz/mcphub/control/syncer/service.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type upstreamRepo interface {
1616
Get(ctx context.Context, id uuid.UUID) (*repo.UpstreamConfig, error)
1717
MarkSyncSuccess(ctx context.Context, id uuid.UUID) error
1818
MarkSyncFailed(ctx context.Context, id uuid.UUID) error
19+
MarkHealthStatus(ctx context.Context, id uuid.UUID, healthy bool) error
1920
}
2021

2122
type toolRepo interface {
@@ -54,6 +55,11 @@ func (s *Service) Sync(ctx context.Context, upstreamID uuid.UUID) error {
5455

5556
tools, err := s.client.ListTools(ctx, upstream)
5657
if err != nil {
58+
_ = s.upstreams.MarkHealthStatus(ctx, upstreamID, false)
59+
_ = s.upstreams.MarkSyncFailed(ctx, upstreamID)
60+
return err
61+
}
62+
if err := s.upstreams.MarkHealthStatus(ctx, upstreamID, true); err != nil {
5763
_ = s.upstreams.MarkSyncFailed(ctx, upstreamID)
5864
return err
5965
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package syncer
2+
3+
import (
4+
"context"
5+
"errors"
6+
"testing"
7+
8+
"github.com/google/uuid"
9+
10+
"github.com/chaitin/MonkeyCode/backend/biz/mcphub/repo"
11+
)
12+
13+
func TestSyncMarksUpstreamHealthy(t *testing.T) {
14+
upstreamID := uuid.New()
15+
upstreams := &upstreamRepoStub{
16+
upstream: &repo.UpstreamConfig{ID: upstreamID, Slug: "images"},
17+
}
18+
service := NewService(upstreams, &toolRepoStub{}, &registryStub{}, &upstreamClientStub{
19+
tools: []repo.UpstreamTool{{Name: "search"}},
20+
})
21+
22+
if err := service.Sync(context.Background(), upstreamID); err != nil {
23+
t.Fatal(err)
24+
}
25+
if upstreams.healthy == nil || !*upstreams.healthy {
26+
t.Fatalf("health status = %v, want healthy", upstreams.healthy)
27+
}
28+
if !upstreams.syncSuccess {
29+
t.Fatal("sync success was not recorded")
30+
}
31+
}
32+
33+
func TestSyncMarksUpstreamUnhealthyWhenListToolsFails(t *testing.T) {
34+
upstreamID := uuid.New()
35+
upstreams := &upstreamRepoStub{
36+
upstream: &repo.UpstreamConfig{ID: upstreamID, Slug: "images"},
37+
}
38+
service := NewService(upstreams, &toolRepoStub{}, &registryStub{}, &upstreamClientStub{
39+
err: errors.New("upstream unavailable"),
40+
})
41+
42+
if err := service.Sync(context.Background(), upstreamID); err == nil {
43+
t.Fatal("Sync() error = nil, want upstream error")
44+
}
45+
if upstreams.healthy == nil || *upstreams.healthy {
46+
t.Fatalf("health status = %v, want unhealthy", upstreams.healthy)
47+
}
48+
if !upstreams.syncFailed {
49+
t.Fatal("sync failure was not recorded")
50+
}
51+
}
52+
53+
type upstreamRepoStub struct {
54+
upstream *repo.UpstreamConfig
55+
healthy *bool
56+
syncSuccess bool
57+
syncFailed bool
58+
}
59+
60+
func (s *upstreamRepoStub) Get(context.Context, uuid.UUID) (*repo.UpstreamConfig, error) {
61+
return s.upstream, nil
62+
}
63+
64+
func (s *upstreamRepoStub) MarkSyncSuccess(context.Context, uuid.UUID) error {
65+
s.syncSuccess = true
66+
return nil
67+
}
68+
69+
func (s *upstreamRepoStub) MarkSyncFailed(context.Context, uuid.UUID) error {
70+
s.syncFailed = true
71+
return nil
72+
}
73+
74+
func (s *upstreamRepoStub) MarkHealthStatus(_ context.Context, _ uuid.UUID, healthy bool) error {
75+
s.healthy = &healthy
76+
return nil
77+
}
78+
79+
type toolRepoStub struct{}
80+
81+
func (s *toolRepoStub) ReplaceByUpstream(context.Context, uuid.UUID, []repo.UpsertToolInput) error {
82+
return nil
83+
}
84+
85+
type registryStub struct{}
86+
87+
func (s *registryStub) Publish(context.Context) error {
88+
return nil
89+
}
90+
91+
type upstreamClientStub struct {
92+
tools []repo.UpstreamTool
93+
err error
94+
}
95+
96+
func (s *upstreamClientStub) ListTools(context.Context, *repo.UpstreamConfig) ([]repo.UpstreamTool, error) {
97+
return s.tools, s.err
98+
}

backend/biz/mcphub/repo/upstream.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,20 @@ func (r *UpstreamRepo) MarkSyncFailed(ctx context.Context, id uuid.UUID) error {
9393
return nil
9494
}
9595

96+
func (r *UpstreamRepo) MarkHealthStatus(ctx context.Context, id uuid.UUID, healthy bool) error {
97+
status := "unhealthy"
98+
if healthy {
99+
status = "healthy"
100+
}
101+
if err := r.db.MCPUpstream.UpdateOneID(id).
102+
SetHealthStatus(status).
103+
SetHealthCheckedAt(time.Now()).
104+
Exec(ctx); err != nil {
105+
return fmt.Errorf("mark upstream %s health status: %w", id, err)
106+
}
107+
return nil
108+
}
109+
96110
func IsUpstreamNotFound(err error) bool {
97111
return errors.Is(err, ErrUpstreamNotFound)
98112
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package repo
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/google/uuid"
8+
_ "github.com/mattn/go-sqlite3"
9+
10+
"github.com/chaitin/MonkeyCode/backend/db/enttest"
11+
)
12+
13+
func TestMarkHealthStatus(t *testing.T) {
14+
client := enttest.Open(t, "sqlite3", "file:mcphub-upstream-health?mode=memory&cache=shared&_fk=1")
15+
defer client.Close()
16+
ctx := context.Background()
17+
upstreamID := uuid.New()
18+
19+
client.MCPUpstream.Create().
20+
SetID(upstreamID).
21+
SetName("images").
22+
SetSlug("images").
23+
SetScope("platform").
24+
SetType("server").
25+
SetURL("https://example.com/mcp").
26+
SaveX(ctx)
27+
28+
repo := NewUpstreamRepo(client)
29+
if err := repo.MarkHealthStatus(ctx, upstreamID, true); err != nil {
30+
t.Fatal(err)
31+
}
32+
row := client.MCPUpstream.GetX(ctx, upstreamID)
33+
if row.HealthStatus != "healthy" || row.HealthCheckedAt == nil {
34+
t.Fatalf("healthy upstream = %+v", row)
35+
}
36+
37+
if err := repo.MarkHealthStatus(ctx, upstreamID, false); err != nil {
38+
t.Fatal(err)
39+
}
40+
row = client.MCPUpstream.GetX(ctx, upstreamID)
41+
if row.HealthStatus != "unhealthy" || row.HealthCheckedAt == nil {
42+
t.Fatalf("unhealthy upstream = %+v", row)
43+
}
44+
}

0 commit comments

Comments
 (0)