Skip to content

Commit 6977a25

Browse files
author
OpenCode CLI User
committed
fix: 处理服务器空响应并完善单元测试
- 修复 message add 命令在服务器返回空响应时的错误处理 - 添加 client 包的单元测试 - 完善 config/types/util 包的单元测试 - 增强 GitHub Actions CI/CD 工作流
1 parent e09fa1b commit 6977a25

File tree

6 files changed

+771
-16
lines changed

6 files changed

+771
-16
lines changed

.github/workflows/go.yml

Lines changed: 204 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,218 @@
1-
# This workflow will build a golang project
2-
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
1+
# GitHub Actions Workflow for oho CLI
2+
# Supports: Auto build/test, Release on tag, Multi-platform, Multi-arch
33

4-
name: Go
4+
name: Go CI/CD
55

66
on:
77
push:
8-
branches: [ "master" ]
8+
branches: [master]
99
pull_request:
10-
branches: [ "master" ]
10+
branches: [master]
11+
workflow_dispatch:
12+
tag:
13+
patterns:
14+
- 'v*'
15+
types: [created]
16+
17+
env:
18+
GO_VERSION: '1.21'
19+
APP_NAME: oho
1120

1221
jobs:
22+
# ===========================================
23+
# Job 1: 代码质量检查
24+
# ===========================================
25+
lint:
26+
name: Lint
27+
runs-on: ubuntu-latest
28+
steps:
29+
- name: Checkout code
30+
uses: actions/checkout@v4
31+
32+
- name: Set up Go
33+
uses: actions/setup-go@v5
34+
with:
35+
go-version: ${{ env.GO_VERSION }}
1336

37+
- name: Download dependencies
38+
run: go mod download
39+
40+
- name: Run golangci-lint
41+
uses: golangci/golangci-lint-action@v6
42+
with:
43+
version: latest
44+
args: --timeout=5m
45+
46+
# ===========================================
47+
# Job 2: 测试
48+
# ===========================================
49+
test:
50+
name: Test
51+
runs-on: ubuntu-latest
52+
steps:
53+
- name: Checkout code
54+
uses: actions/checkout@v4
55+
56+
- name: Set up Go
57+
uses: actions/setup-go@v5
58+
with:
59+
go-version: ${{ env.GO_VERSION }}
60+
61+
- name: Download dependencies
62+
run: go mod download
63+
64+
- name: Run tests
65+
run: |
66+
go test -v -race -coverprofile=coverage.out ./...
67+
68+
- name: Upload coverage to Codecov
69+
if: github.event_name == 'pull_request'
70+
uses: codecov/codecov-action@v4
71+
with:
72+
files: ./coverage.out
73+
flags: unittests
74+
name: codecov-umbrella
75+
76+
# ===========================================
77+
# Job 3: 构建 (PR 和 master 推送时)
78+
# ===========================================
1479
build:
80+
name: Build
1581
runs-on: ubuntu-latest
82+
needs: [lint, test]
83+
strategy:
84+
matrix:
85+
goos: [linux]
86+
goarch: [amd64, arm64]
1687
steps:
17-
- uses: actions/checkout@v4
88+
- name: Checkout code
89+
uses: actions/checkout@v4
90+
91+
- name: Set up Go
92+
uses: actions/setup-go@v5
93+
with:
94+
go-version: ${{ env.GO_VERSION }}
1895

19-
- name: Set up Go
20-
uses: actions/setup-go@v4
21-
with:
22-
go-version: '1.20'
96+
- name: Download dependencies
97+
run: go mod download
2398

24-
- name: Build
25-
run: go build -v ./...
99+
- name: Build
100+
env:
101+
GOOS: ${{ matrix.goos }}
102+
GOARCH: ${{ matrix.goarch }}
103+
run: |
104+
go build -ldflags="-s -w" -o bin/${APP_NAME}-${GOOS}-${GOARCH} ./cmd
26105
27-
- name: Test
28-
run: go test -v ./...
29-
30-
106+
- name: Upload artifact
107+
uses: actions/upload-artifact@v4
108+
with:
109+
name: ${{ env.APP_NAME }}-${{ matrix.goos }}-${{ matrix.goarch }}
110+
path: bin/${{ env.APP_NAME }}-${{ matrix.goos }}-${{ matrix.goarch }}
111+
retention-days: 7
112+
113+
# ===========================================
114+
# Job 4: Release 发布 (仅在 tag 推送时)
115+
# ===========================================
116+
release:
117+
name: Release
118+
runs-on: ubuntu-latest
119+
needs: [lint, test]
120+
if: startsWith(github.ref, 'refs/tags/v')
121+
permissions:
122+
contents: write
123+
strategy:
124+
fail-fast: false
125+
matrix:
126+
include:
127+
# Linux
128+
- os: ubuntu-latest
129+
goos: linux
130+
goarch: amd64
131+
ext: ''
132+
- os: ubuntu-latest
133+
goos: linux
134+
goarch: arm64
135+
ext: ''
136+
# Windows
137+
- os: windows-latest
138+
goos: windows
139+
goarch: amd64
140+
ext: .exe
141+
# macOS
142+
- os: macos-latest
143+
goos: darwin
144+
goarch: amd64
145+
ext: ''
146+
- os: macos-latest
147+
goos: darwin
148+
goarch: arm64
149+
ext: ''
150+
steps:
151+
- name: Checkout code
152+
uses: actions/checkout@v4
153+
154+
- name: Set up Go
155+
uses: actions/setup-go@v5
156+
with:
157+
go-version: ${{ env.GO_VERSION }}
158+
159+
- name: Download dependencies
160+
run: go mod download
161+
162+
- name: Build Release
163+
env:
164+
GOOS: ${{ matrix.goos }}
165+
GOARCH: ${{ matrix.goarch }}
166+
run: |
167+
OUTPUT_NAME="${{ env.APP_NAME }}-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }}"
168+
go build -ldflags="-s -w -X main.Version=${GITHUB_REF#refs/tags/}" -o "${OUTPUT_NAME}" ./cmd
169+
ls -la
170+
171+
- name: Upload Release Asset
172+
uses: softprops/action-gh-release@v1
173+
with:
174+
tag_name: ${{ github.ref }}
175+
name: ${{ env.APP_NAME }} ${{ github.ref }}
176+
body: |
177+
## Release Notes
178+
179+
### Changes
180+
- Automated build for ${{ matrix.goos }}/${{ matrix.goarch }}
181+
182+
### Downloads
183+
- ${{ env.APP_NAME }}-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }}
184+
files: |
185+
${{ env.APP_NAME }}-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }}
186+
env:
187+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
188+
189+
- name: Create checksums
190+
run: |
191+
${{ matrix.goos == 'windows' && 'certutil -hashfile' || 'sha256sum' }} ${{ env.APP_NAME }}-${{ matrix.goos }}-${{ matrix.goarch }}${{ matrix.ext }} | tee checksums.txt
192+
193+
- name: Upload Checksums
194+
uses: softprops/action-gh-release@v1
195+
with:
196+
tag_name: ${{ github.ref }}
197+
body_path: checksums.txt
198+
env:
199+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
200+
201+
# ===========================================
202+
# Job 5: 通知 (发布成功后)
203+
# ===========================================
204+
notify:
205+
name: Notify
206+
runs-on: ubuntu-latest
207+
needs: [release]
208+
if: startsWith(github.ref, 'refs/tags/v')
209+
steps:
210+
- name: Create GitHub Release summary
211+
run: |
212+
echo "## 🎉 Release Published" >> $GITHUB_STEP_SUMMARY
213+
echo "" >> $GITHUB_STEP_SUMMARY
214+
echo "**Version:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
215+
echo "**Commit:** ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
216+
echo "" >> $GITHUB_STEP_SUMMARY
217+
echo "### Downloads" >> $GITHUB_STEP_SUMMARY
218+
echo "- [Release Page](${{ github.server_url }}/${{ github.repository }}/releases/tag/${{ github.ref_name }})" >> $GITHUB_STEP_SUMMARY

oho/cmd/message/message.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,12 @@ var addCmd = &cobra.Command{
136136
return err
137137
}
138138

139+
// 服务器返回空响应时处理
140+
if len(resp) == 0 {
141+
fmt.Println("消息已发送")
142+
return nil
143+
}
144+
139145
var result types.MessageWithParts
140146
if err := json.Unmarshal(resp, &result); err != nil {
141147
return err

oho/internal/client/client_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"net/http"
7+
"net/http/httptest"
8+
"testing"
9+
10+
"github.com/anomalyco/oho/internal/config"
11+
)
12+
13+
// 初始化配置
14+
func init() {
15+
_ = config.Init()
16+
}
17+
18+
func TestNewClient(t *testing.T) {
19+
c := NewClient()
20+
if c == nil {
21+
t.Fatal("NewClient() returned nil")
22+
}
23+
if c.httpClient == nil {
24+
t.Fatal("httpClient is nil")
25+
}
26+
}
27+
28+
func TestClientGetSuccess(t *testing.T) {
29+
// 创建模拟服务器
30+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
31+
if r.Method != http.MethodGet {
32+
t.Errorf("Expected GET, got %s", r.Method)
33+
}
34+
w.Header().Set("Content-Type", "application/json")
35+
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
36+
}))
37+
defer server.Close()
38+
39+
c := &Client{
40+
baseURL: server.URL,
41+
username: "test",
42+
password: "test",
43+
httpClient: &http.Client{},
44+
}
45+
46+
resp, err := c.Get(context.Background(), "/test")
47+
if err != nil {
48+
t.Fatalf("Get failed: %v", err)
49+
}
50+
51+
var result map[string]string
52+
if err := json.Unmarshal(resp, &result); err != nil {
53+
t.Fatalf("Unmarshal failed: %v", err)
54+
}
55+
56+
if result["status"] != "ok" {
57+
t.Errorf("Expected status ok, got %s", result["status"])
58+
}
59+
}
60+
61+
func TestClientPostSuccess(t *testing.T) {
62+
// 测试 JSON 编组
63+
req := map[string]string{"name": "test"}
64+
data, err := json.Marshal(req)
65+
if err != nil {
66+
t.Fatalf("Marshal failed: %v", err)
67+
}
68+
69+
var result map[string]string
70+
if err := json.Unmarshal(data, &result); err != nil {
71+
t.Fatalf("Unmarshal failed: %v", err)
72+
}
73+
74+
if result["name"] != "test" {
75+
t.Errorf("Expected name test, got %s", result["name"])
76+
}
77+
}
78+
79+
func TestClientEmptyResponse(t *testing.T) {
80+
// 创建返回空响应的服务器
81+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
82+
w.WriteHeader(http.StatusOK)
83+
}))
84+
defer server.Close()
85+
86+
c := &Client{
87+
baseURL: server.URL,
88+
username: "test",
89+
password: "test",
90+
httpClient: &http.Client{},
91+
}
92+
93+
resp, err := c.Get(context.Background(), "/test")
94+
if err != nil {
95+
t.Fatalf("Get failed: %v", err)
96+
}
97+
98+
// 空响应应该返回空字节切片
99+
if len(resp) != 0 {
100+
t.Errorf("Expected empty response, got %d bytes", len(resp))
101+
}
102+
}
103+
104+
func TestClientErrorResponse(t *testing.T) {
105+
// 创建返回错误的服务器
106+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
107+
http.Error(w, "Not Found", http.StatusNotFound)
108+
}))
109+
defer server.Close()
110+
111+
c := &Client{
112+
baseURL: server.URL,
113+
username: "test",
114+
password: "test",
115+
httpClient: &http.Client{},
116+
}
117+
118+
_, err := c.Get(context.Background(), "/test")
119+
if err == nil {
120+
t.Fatal("Expected error, got nil")
121+
}
122+
}

0 commit comments

Comments
 (0)