Skip to content

Commit ef9ce3e

Browse files
committed
add unit tests
1 parent 5611299 commit ef9ce3e

File tree

8 files changed

+91
-34
lines changed

8 files changed

+91
-34
lines changed

cmd/convert_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2023 API Testing Authors.
2+
Copyright 2023-2025 API Testing Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ import (
2121
"io"
2222
"os"
2323
"path"
24+
"strconv"
2425
"testing"
2526
"time"
2627

@@ -36,7 +37,8 @@ func TestConvert(t *testing.T) {
3637
c.SetOut(io.Discard)
3738

3839
t.Run("normal", func(t *testing.T) {
39-
tmpFile := path.Join(os.TempDir(), time.Now().String())
40+
now := strconv.Itoa(int(time.Now().Unix()))
41+
tmpFile := path.Join(os.TempDir(), now)
4042
defer os.RemoveAll(tmpFile)
4143

4244
c.SetArgs([]string{"convert", "-p=testdata/simple-suite.yaml", "--converter=jmeter", "--target", tmpFile})

cmd/extension_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package cmd
1818

1919
import (
20+
"context"
2021
"fmt"
2122
"io"
2223
"os"
@@ -37,7 +38,7 @@ func TestExtensionCmd(t *testing.T) {
3738

3839
t.Run("normal", func(t *testing.T) {
3940
d := downloader.NewStoreDownloader()
40-
server := mock.NewInMemoryServer(0)
41+
server := mock.NewInMemoryServer(context.Background(), 0)
4142

4243
err := server.Start(mock.NewLocalFileReader("../pkg/downloader/testdata/registry.yaml"), "/v2")
4344
assert.NoError(t, err)

cmd/mock.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 API Testing Authors.
2+
Copyright 2024-2025 API Testing Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -53,6 +53,9 @@ func createMockCmd() (c *cobra.Command) {
5353
func (o *mockOption) runE(c *cobra.Command, args []string) (err error) {
5454
reader := mock.NewLocalFileReader(args[0])
5555
server := mock.NewInMemoryServer(c.Context(), o.port)
56+
if o.metrics {
57+
server.EnableMetrics()
58+
}
5659
if err = server.Start(reader, o.prefix); err != nil {
5760
return
5861
}
@@ -61,7 +64,6 @@ func (o *mockOption) runE(c *cobra.Command, args []string) (err error) {
6164
signal.Notify(clean, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
6265
printLocalIPs(c, o.port)
6366
if o.metrics {
64-
server.EnableMetrics()
6567
c.Printf("Metrics available at http://localhost:%d%s/metrics\n", o.port, o.prefix)
6668
}
6769

cmd/server_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ func TestFrontEndHandlerWithLocation(t *testing.T) {
210210
resp := newFakeResponseWriter()
211211

212212
opt.getAtestBinary(resp, req, map[string]string{})
213-
assert.Equal(t, `failed to read "atest": open : no such file or directory`, resp.GetBody().String())
213+
assert.Contains(t, resp.GetBody().String(), `failed to read "atest"`)
214214
})
215215
}
216216

pkg/mock/in_memory.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 API Testing Authors.
2+
Copyright 2024-2025 API Testing Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -74,6 +74,7 @@ func (s *inMemoryServer) SetupHandler(reader Reader, prefix string) (handler htt
7474
s.mux = mux.NewRouter().PathPrefix(prefix).Subrouter()
7575
s.prefix = prefix
7676
handler = s.mux
77+
s.metrics.AddMetricsHandler(s.mux)
7778
err = s.Load()
7879
return
7980
}
@@ -109,22 +110,31 @@ func (s *inMemoryServer) Load() (err error) {
109110
memLogger.Info("start to proxy", "target", proxy.Target)
110111
s.mux.HandleFunc(proxy.Path, func(w http.ResponseWriter, req *http.Request) {
111112
api := fmt.Sprintf("%s/%s", proxy.Target, strings.TrimPrefix(req.URL.Path, s.prefix))
113+
api, err = render.Render("proxy api", api, s)
114+
if err != nil {
115+
w.WriteHeader(http.StatusInternalServerError)
116+
memLogger.Error(err, "failed to render proxy api")
117+
return
118+
}
112119
memLogger.Info("redirect to", "target", api)
113120

114121
targetReq, err := http.NewRequestWithContext(req.Context(), req.Method, api, req.Body)
115122
if err != nil {
123+
w.WriteHeader(http.StatusInternalServerError)
116124
memLogger.Error(err, "failed to create proxy request")
117125
return
118126
}
119127

120128
resp, err := http.DefaultClient.Do(targetReq)
121129
if err != nil {
130+
w.WriteHeader(http.StatusInternalServerError)
122131
memLogger.Error(err, "failed to do proxy request")
123132
return
124133
}
125134

126135
data, err := io.ReadAll(resp.Body)
127136
if err != nil {
137+
w.WriteHeader(http.StatusInternalServerError)
128138
memLogger.Error(err, "failed to read response body")
129139
return
130140
}

pkg/mock/in_memory_test.go

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2024 API Testing Authors.
2+
Copyright 2024-2025 API Testing Authors.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package mock
1717

1818
import (
1919
"bytes"
20+
"context"
2021
"io"
2122
"net/http"
2223
"strings"
@@ -27,7 +28,8 @@ import (
2728
)
2829

2930
func TestInMemoryServer(t *testing.T) {
30-
server := NewInMemoryServer(0)
31+
server := NewInMemoryServer(context.Background(), 0)
32+
server.EnableMetrics()
3133

3234
err := server.Start(NewLocalFileReader("testdata/api.yaml"), "/mock")
3335
assert.NoError(t, err)
@@ -165,28 +167,28 @@ func TestInMemoryServer(t *testing.T) {
165167
})
166168

167169
t.Run("not found config file", func(t *testing.T) {
168-
server := NewInMemoryServer(0)
170+
server := NewInMemoryServer(context.Background(), 0)
169171
err := server.Start(NewLocalFileReader("fake"), "/")
170172
assert.Error(t, err)
171173
})
172174

173175
t.Run("invalid webhook", func(t *testing.T) {
174-
server := NewInMemoryServer(0)
176+
server := NewInMemoryServer(context.Background(), 0)
175177
err := server.Start(NewInMemoryReader(`webhooks:
176178
- timer: aa
177179
name: fake`), "/")
178180
assert.Error(t, err)
179181
})
180182

181183
t.Run("missing name or timer in webhook", func(t *testing.T) {
182-
server := NewInMemoryServer(0)
184+
server := NewInMemoryServer(context.Background(), 0)
183185
err := server.Start(NewInMemoryReader(`webhooks:
184186
- timer: 1s`), "/")
185187
assert.Error(t, err)
186188
})
187189

188190
t.Run("invalid webhook payload", func(t *testing.T) {
189-
server := NewInMemoryServer(0)
191+
server := NewInMemoryServer(context.Background(), 0)
190192
err := server.Start(NewInMemoryReader(`webhooks:
191193
- name: invalid
192194
timer: 1ms
@@ -196,7 +198,7 @@ func TestInMemoryServer(t *testing.T) {
196198
})
197199

198200
t.Run("invalid webhook api template", func(t *testing.T) {
199-
server := NewInMemoryServer(0)
201+
server := NewInMemoryServer(context.Background(), 0)
200202
err := server.Start(NewInMemoryReader(`webhooks:
201203
- name: invalid
202204
timer: 1ms
@@ -205,4 +207,20 @@ func TestInMemoryServer(t *testing.T) {
205207
path: "{{.fake"`), "/")
206208
assert.NoError(t, err)
207209
})
210+
211+
t.Run("proxy", func(t *testing.T) {
212+
resp, err = http.Get(api + "/v1/myProjects")
213+
assert.NoError(t, err)
214+
assert.Equal(t, http.StatusOK, resp.StatusCode)
215+
216+
resp, err = http.Get(api + "/v1/invalid-template")
217+
assert.NoError(t, err)
218+
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
219+
})
220+
221+
t.Run("metrics", func(t *testing.T) {
222+
resp, err = http.Get(api + "/metrics")
223+
assert.NoError(t, err)
224+
assert.Equal(t, http.StatusOK, resp.StatusCode)
225+
})
208226
}

pkg/mock/metrics.go

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@ limitations under the License.
1616
package mock
1717

1818
import (
19+
"encoding/json"
1920
"net/http"
2021
"sync"
22+
"time"
23+
24+
"github.com/gorilla/mux"
2125
)
2226

27+
var _ RequestMetrics = &NoopMetrics{}
28+
2329
// NoopMetrics implements RequestMetrics but does nothing
2430
type NoopMetrics struct{}
2531

@@ -32,56 +38,69 @@ func NewNoopMetrics() *NoopMetrics {
3238
func (m *NoopMetrics) RecordRequest(path string) {}
3339

3440
// GetMetrics implements RequestMetrics but returns empty map
35-
func (m *NoopMetrics) GetMetrics() map[string]int {
36-
return make(map[string]int)
41+
func (m *NoopMetrics) GetMetrics() MetricData {
42+
return MetricData{}
3743
}
3844

3945
// AddMetricsHandler implements RequestMetrics but does nothing
40-
func (m *NoopMetrics) AddMetricsHandler(mux *http.ServeMux, prefix string) {}
46+
func (m *NoopMetrics) AddMetricsHandler(mux MetricsHandler) {}
47+
48+
type MetricsHandler interface {
49+
HandleFunc(path string, f func(http.ResponseWriter, *http.Request)) *mux.Route
50+
}
51+
52+
type MetricData struct {
53+
FirstRequestTime time.Time
54+
LastRequestTime time.Time
55+
Requests map[string]int
56+
}
4157

4258
// RequestMetrics represents an interface for collecting request metrics
4359
type RequestMetrics interface {
4460
RecordRequest(path string)
45-
GetMetrics() map[string]int
46-
AddMetricsHandler(mux *http.ServeMux, prefix string)
61+
GetMetrics() MetricData
62+
AddMetricsHandler(MetricsHandler)
4763
}
4864

65+
var _ RequestMetrics = &InMemoryMetrics{}
66+
4967
// InMemoryMetrics implements RequestMetrics with in-memory storage
5068
type InMemoryMetrics struct {
51-
requests map[string]int
52-
mu sync.RWMutex
69+
MetricData
70+
mu sync.RWMutex
5371
}
5472

5573
// NewInMemoryMetrics creates a new InMemoryMetrics instance
5674
func NewInMemoryMetrics() *InMemoryMetrics {
5775
return &InMemoryMetrics{
58-
requests: make(map[string]int),
76+
MetricData: MetricData{
77+
Requests: make(map[string]int),
78+
},
5979
}
6080
}
6181

6282
// RecordRequest records a request for the given path
6383
func (m *InMemoryMetrics) RecordRequest(path string) {
6484
m.mu.Lock()
6585
defer m.mu.Unlock()
66-
m.requests[path]++
86+
m.Requests[path]++
87+
if m.FirstRequestTime.IsZero() {
88+
m.FirstRequestTime = time.Now()
89+
}
90+
m.LastRequestTime = time.Now()
6791
}
6892

6993
// GetMetrics returns a copy of the current metrics
70-
func (m *InMemoryMetrics) GetMetrics() map[string]int {
94+
func (m *InMemoryMetrics) GetMetrics() MetricData {
7195
m.mu.RLock()
7296
defer m.mu.RUnlock()
73-
74-
// Return a copy to avoid map races
75-
result := make(map[string]int)
76-
for k, v := range m.requests {
77-
result[k] = v
78-
}
79-
return result
97+
return m.MetricData
8098
}
8199

82-
func (m *InMemoryMetrics) AddMetricsHandler(mux *http.ServeMux, prefix string) {
100+
func (m *InMemoryMetrics) AddMetricsHandler(mux MetricsHandler) {
83101
// Add metrics endpoint
84-
mux.HandleFunc(prefix+"/metrics", func(w http.ResponseWriter, r *http.Request) {
85-
// metrics handling code
102+
mux.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
103+
metrics := m.GetMetrics()
104+
_ = json.NewEncoder(w).Encode(metrics)
86105
})
87106
}

pkg/mock/testdata/api.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ items:
4949
"status": "success"
5050
}]
5151
}
52+
proxies:
53+
- path: /v1/myProjects
54+
target: http://localhost:{{.GetPort}}
55+
- path: /v1/invalid-template
56+
target: http://localhost:{{.GetPort}
5257
webhooks:
5358
- timer: 1ms
5459
name: baidu

0 commit comments

Comments
 (0)