diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..672758a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,78 @@ +name: Test + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + test: + name: Test Go Module + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.23' + cache: true + + - name: Run tests + run: go test -race ./... + + + + build: + name: Build Binary + runs-on: ubuntu-latest + needs: [test] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.23' + + - name: Build application + run: | + mkdir -p bin + go build -o bin/mcp-server ./cmd/mcp-server + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: mcp-server-binary + path: bin/mcp-server + + docker: + name: Build Docker Image + runs-on: ubuntu-latest + needs: [test] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Build Docker image + run: | + docker build -t weave-toolkit/mcp-server:latest . + + - name: Save Docker image + run: | + docker save weave-toolkit/mcp-server:latest -o mcp-server-image.tar + + - name: Upload Docker image artifact + uses: actions/upload-artifact@v4 + with: + name: mcp-server-docker-image + path: mcp-server-image.tar \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..de245f6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,83 @@ +name: Release + +on: + push: + tags: + - 'v*' + +jobs: + release: + name: Build and Release + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v4 + with: + go-version: '1.23' + cache: true + + - name: Build + run: | + mkdir -p bin + go build -o bin/mcp-server ./cmd/mcp-server + + - name: Create Release + uses: softprops/action-gh-release@v1 + with: + files: bin/mcp-server + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./bin/mcp-server + asset_name: mcp-server-${{ github.ref_name }} + asset_content_type: application/octet-stream + + docker-release: + name: Build and Push Docker Image + runs-on: ubuntu-latest + needs: release + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v4 + with: + images: weave-toolkit/mcp-server + tags: | + type=ref,event=tag + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + latest + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Docker Hub description + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + repository: weave-toolkit/mcp-server \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c91079c..eb062ae 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,7 +32,6 @@ WORKDIR /app COPY --from=builder /app/mcp-server . # 复制配置文件 -COPY .env ./ COPY tool-config.json ./ # 更改文件所有权 diff --git a/go.mod b/go.mod index 963943c..187c1fc 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,14 @@ go 1.24.9 require ( github.com/gin-gonic/gin v1.11.0 github.com/rs/zerolog v1.34.0 + github.com/stretchr/testify v1.11.1 ) require ( github.com/bytedance/sonic v1.14.0 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -24,6 +26,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.54.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -37,6 +40,7 @@ require ( golang.org/x/text v0.27.0 // indirect golang.org/x/tools v0.34.0 // indirect google.golang.org/protobuf v1.36.9 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( diff --git a/go.sum b/go.sum index 0f4c631..fa650cf 100644 --- a/go.sum +++ b/go.sum @@ -96,6 +96,7 @@ golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/test/calculator_test.go b/test/calculator_test.go new file mode 100644 index 0000000..d18815a --- /dev/null +++ b/test/calculator_test.go @@ -0,0 +1,145 @@ +package test + +import ( + "context" + "encoding/json" + "testing" + + "Weave-Toolkit/internal/tools" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCalculatorTool(t *testing.T) { + calculator := &tools.CalculatorTool{} + + tests := []struct { + name string + args tools.CalculatorArgs + expected float64 + hasError bool + }{ + { + name: "加法运算", + args: tools.CalculatorArgs{ + Operation: "add", + A: 10, + B: 20, + }, + expected: 30, + }, + { + name: "减法运算", + args: tools.CalculatorArgs{ + Operation: "subtract", + A: 50, + B: 30, + }, + expected: 20, + }, + { + name: "乘法运算", + args: tools.CalculatorArgs{ + Operation: "multiply", + A: 5, + B: 6, + }, + expected: 30, + }, + { + name: "除法运算", + args: tools.CalculatorArgs{ + Operation: "divide", + A: 100, + B: 4, + }, + expected: 25, + }, + { + name: "除零错误", + args: tools.CalculatorArgs{ + Operation: "divide", + A: 10, + B: 0, + }, + hasError: true, + }, + { + name: "无效操作", + args: tools.CalculatorArgs{ + Operation: "invalid", + A: 10, + B: 20, + }, + hasError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + argsJSON, err := json.Marshal(tt.args) + require.NoError(t, err) + + result, err := calculator.Execute(context.Background(), argsJSON) + + if tt.hasError { + assert.Error(t, err) + return + } + + require.NoError(t, err) + + var calcResult tools.CalculatorResult + err = json.Unmarshal(result, &calcResult) + require.NoError(t, err) + + assert.Equal(t, tt.expected, calcResult.Result) + }) + } +} + +func TestCalculatorToolWithOperands(t *testing.T) { + calculator := &tools.CalculatorTool{} + + tests := []struct { + name string + operands []float64 + operation string + expected float64 + }{ + { + name: "使用操作数数组进行加法", + operands: []float64{15, 25}, + operation: "add", + expected: 40, + }, + { + name: "使用操作数数组进行乘法", + operands: []float64{3, 7}, + operation: "multiply", + expected: 21, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args := tools.CalculatorArgs{ + Operation: tt.operation, + Operands: tt.operands, + } + + argsJSON, err := json.Marshal(args) + require.NoError(t, err) + + result, err := calculator.Execute(context.Background(), argsJSON) + require.NoError(t, err) + + var calcResult tools.CalculatorResult + err = json.Unmarshal(result, &calcResult) + require.NoError(t, err) + + assert.Equal(t, tt.expected, calcResult.Result) + }) + } +} diff --git a/test/stream_text_processor_test.go b/test/stream_text_processor_test.go new file mode 100644 index 0000000..c4fd459 --- /dev/null +++ b/test/stream_text_processor_test.go @@ -0,0 +1,126 @@ +package test + +import ( + "context" + "encoding/json" + "testing" + + "Weave-Toolkit/internal/tools" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStreamTextProcessor(t *testing.T) { + processor := &tools.StreamTextProcessor{} + + tests := []struct { + name string + text string + operation string + expected interface{} + hasError bool + }{ + { + name: "文本分割", + text: "hello world test", + operation: "split", + expected: []interface{}{"hello", "world", "test"}, + }, + { + name: "文本反转", + text: "hello", + operation: "reverse", + expected: "olleh", + }, + { + name: "字符计数", + text: "hello world", + operation: "count", + expected: map[string]interface{}{ + "characters": float64(11), + "words": float64(2), + "lines": float64(1), + }, + }, + { + name: "文本分析", + text: "Hello World!", + operation: "analyze", + expected: map[string]interface{}{ + "length": float64(12), + "word_count": float64(2), + "line_count": float64(1), + "has_uppercase": true, + "has_lowercase": true, + }, + }, + { + name: "无效操作", + text: "test", + operation: "invalid", + hasError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args := map[string]interface{}{ + "text": tt.text, + "operation": tt.operation, + } + + argsJSON, err := json.Marshal(args) + require.NoError(t, err) + + result, err := processor.Execute(context.Background(), argsJSON) + + if tt.hasError { + assert.Error(t, err) + return + } + + require.NoError(t, err) + + var textResult tools.StreamTextResult + err = json.Unmarshal(result, &textResult) + require.NoError(t, err) + + assert.Equal(t, tt.text, textResult.OriginalText) + assert.Equal(t, tt.operation, textResult.Operation) + + // 使用JSON序列化比较结果,避免类型不匹配问题 + expectedJSON, _ := json.Marshal(tt.expected) + actualJSON, _ := json.Marshal(textResult.Result) + assert.JSONEq(t, string(expectedJSON), string(actualJSON)) + }) + } +} + +func TestStreamTextProcessorDefaultOperation(t *testing.T) { + processor := &tools.StreamTextProcessor{} + + // 测试默认操作(analyze) + args := map[string]interface{}{ + "text": "Test Text", + } + + argsJSON, err := json.Marshal(args) + require.NoError(t, err) + + result, err := processor.Execute(context.Background(), argsJSON) + require.NoError(t, err) + + var textResult tools.StreamTextResult + err = json.Unmarshal(result, &textResult) + require.NoError(t, err) + + assert.Equal(t, "Test Text", textResult.OriginalText) + assert.Equal(t, "analyze", textResult.Operation) + + // 验证分析结果 + analysis, ok := textResult.Result.(map[string]interface{}) + require.True(t, ok) + assert.Equal(t, float64(9), analysis["length"]) + assert.Equal(t, float64(2), analysis["word_count"]) +}