Skip to content

Commit ce1f79f

Browse files
authored
feat: support getting page of static files (#814)
* feat: add extension to getting page of static * fix some issue of getting static files * fix unit tests * add more unit tests * fix the unit test errors --------- Co-authored-by: rick <[email protected]>
1 parent 30d03eb commit ce1f79f

20 files changed

+688
-226
lines changed

cmd/server.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ func (o *serverOption) runE(cmd *cobra.Command, args []string) (err error) {
366366
mux.HandlePath(http.MethodGet, "/healthz", frontEndHandlerWithLocation(o.consolePath))
367367
mux.HandlePath(http.MethodGet, "/favicon.ico", frontEndHandlerWithLocation(o.consolePath))
368368
mux.HandlePath(http.MethodGet, "/swagger.json", frontEndHandlerWithLocation(o.consolePath))
369+
mux.HandlePath(http.MethodGet, "/data/{data}", o.dataFromExtension(remoteServer.(server.UIExtensionServer)))
369370
mux.HandlePath(http.MethodGet, "/get", o.getAtestBinary)
370371
mux.HandlePath(http.MethodPost, "/runner/{suite}/{case}", service.WebRunnerHandler)
371372
mux.HandlePath(http.MethodGet, "/api/v1/sbom", service.SBomHandler)
@@ -531,6 +532,27 @@ func debugHandler(mux *runtime.ServeMux, remoteServer server.RunnerServer) {
531532
})
532533
}
533534

535+
func (o *serverOption) dataFromExtension(extServer server.UIExtensionServer) func(w http.ResponseWriter,
536+
r *http.Request, pathParams map[string]string) {
537+
return func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
538+
ctx := r.Context()
539+
for k, v := range r.Header {
540+
if !strings.HasPrefix(k, "X-Extension-") {
541+
continue
542+
}
543+
ctx = context.WithValue(ctx, k, v)
544+
}
545+
result, err := extServer.GetPageOfStatic(ctx, &server.SimpleName{
546+
Name: pathParams["data"],
547+
})
548+
if err == nil {
549+
w.Write([]byte(result.GetMessage()))
550+
} else {
551+
w.Write([]byte(err.Error()))
552+
}
553+
}
554+
}
555+
534556
func (o *serverOption) getAtestBinary(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
535557
name := util.EmptyThenDefault(r.URL.Query().Get("name"), "atest")
536558

cmd/server_test.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2023-2024 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.
@@ -145,6 +145,15 @@ func TestFrontEndHandlerWithLocation(t *testing.T) {
145145
assert.Equal(t, "ok", resp.GetBody().String())
146146
})
147147

148+
t.Run("swagger", func(t *testing.T) {
149+
req, err := http.NewRequest(http.MethodGet, "/swagger.json", nil)
150+
assert.NoError(t, err)
151+
152+
resp := newFakeResponseWriter()
153+
handler(resp, req, map[string]string{})
154+
assert.Equal(t, string(server.SwaggerJSON), resp.GetBody().String())
155+
})
156+
148157
t.Run("pprof", func(t *testing.T) {
149158
apis := []string{"", "cmdline", "symbol",
150159
"trace", "profile",
@@ -339,6 +348,24 @@ func TestStartPlugins(t *testing.T) {
339348
})
340349
}
341350

351+
func TestDataFromExtension(t *testing.T) {
352+
opt := &serverOption{}
353+
354+
handler := opt.dataFromExtension(server.UnimplementedUIExtensionServer{})
355+
t.Run("not found", func(t *testing.T) {
356+
req, err := http.NewRequest(http.MethodGet, "/data/fake", nil)
357+
req.Header.Set("X-Extension-Fake", "fake")
358+
req.Header.Set("Content-Type", "application/json")
359+
assert.NoError(t, err)
360+
361+
resp := newFakeResponseWriter()
362+
handler(resp, req, map[string]string{
363+
"data": "fake",
364+
})
365+
assert.Contains(t, resp.GetBody().String(), "not implemented")
366+
})
367+
}
368+
342369
type fakeResponseWriter struct {
343370
buf *bytes.Buffer
344371
header http.Header

console/atest-ui/vite.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ export default defineConfig({
6868
target: 'http://127.0.0.1:8080',
6969
changeOrigin: true,
7070
},
71+
'/data': {
72+
target: 'http://127.0.0.1:8080',
73+
changeOrigin: true,
74+
},
7175
},
7276
},
7377
})

pkg/server/remote_server.go

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2023-2024 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.
@@ -1405,9 +1405,12 @@ func (s *server) GetTheme(ctx context.Context, in *SimpleName) (result *CommonRe
14051405
loader := s.getLoader(ctx)
14061406
defer loader.Close()
14071407

1408-
result = &CommonResult{}
1408+
result = &CommonResult{
1409+
Success: true,
1410+
}
14091411
result.Message, err = loader.GetTheme(in.Name)
14101412
if err != nil {
1413+
result.Success = false
14111414
result.Message = fmt.Sprintf("failed to get theme: %v", err)
14121415
}
14131416
return
@@ -1420,9 +1423,9 @@ func (s *server) GetBindings(ctx context.Context, _ *Empty) (result *SimpleList,
14201423
result = &SimpleList{}
14211424
var bindings []string
14221425
if bindings, err = loader.GetBindings(); err == nil {
1423-
for _, theme := range bindings {
1426+
for _, binding := range bindings {
14241427
result.Data = append(result.Data, &Pair{
1425-
Key: theme,
1428+
Key: binding,
14261429
Value: "",
14271430
})
14281431
}
@@ -1434,9 +1437,12 @@ func (s *server) GetBinding(ctx context.Context, in *SimpleName) (result *Common
14341437
loader := s.getLoader(ctx)
14351438
defer loader.Close()
14361439

1437-
result = &CommonResult{}
1440+
result = &CommonResult{
1441+
Success: true,
1442+
}
14381443
result.Message, err = loader.GetBinding(in.Name)
14391444
if err != nil {
1445+
result.Success = false
14401446
result.Message = fmt.Sprintf("failed to get binding: %v", err)
14411447
}
14421448
return
@@ -1531,6 +1537,36 @@ func (s *server) GetPageOfCSS(ctx context.Context, in *SimpleName) (result *Comm
15311537
return
15321538
}
15331539

1540+
func (s *server) GetPageOfStatic(ctx context.Context, in *SimpleName) (result *CommonResult, err error) {
1541+
result = &CommonResult{}
1542+
extNameInter := ctx.Value("X-Extension-Name")
1543+
if extNameInter == nil {
1544+
result.Message = "X-Extension-Name is required"
1545+
result.Success = false
1546+
return
1547+
}
1548+
1549+
var extName string
1550+
switch v := extNameInter.(type) {
1551+
case []string:
1552+
extName = v[0]
1553+
case string:
1554+
extName = v
1555+
}
1556+
1557+
if loader, ok := uiExtensionLoaders[extName]; ok {
1558+
if js, err := loader.GetPageOfStatic(in.Name); err == nil {
1559+
result.Message = js
1560+
result.Success = true
1561+
} else {
1562+
result.Message = err.Error()
1563+
}
1564+
} else {
1565+
result.Message = fmt.Sprintf("not found loader for %s", extName)
1566+
}
1567+
return
1568+
}
1569+
15341570
// implement the mock server
15351571

15361572
// Start starts the mock server
@@ -1561,9 +1597,12 @@ func (s *mockServerController) Reload(ctx context.Context, in *MockConfig) (repl
15611597
case "memory":
15621598
s.mockWriter = mock.NewInMemoryReader(in.Config)
15631599
case "localFile":
1600+
if in.StoreLocalFile == "" {
1601+
return nil, errors.New("StoreLocalFile is required")
1602+
}
15641603
s.mockWriter = mock.NewLocalFileReader(in.StoreLocalFile)
1565-
case "remote":
1566-
case "url":
1604+
default:
1605+
return nil, fmt.Errorf("unsupported store kind: %s", in.StoreKind)
15671606
}
15681607
s.config = in
15691608

pkg/server/remote_server_test.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
_ "embed"
3232

3333
"github.com/h2non/gock"
34+
"github.com/linuxsuren/api-testing/pkg/mock"
3435
atest "github.com/linuxsuren/api-testing/pkg/testing"
3536
"github.com/linuxsuren/api-testing/pkg/util"
3637
"github.com/linuxsuren/api-testing/sample"
@@ -966,6 +967,10 @@ func getRemoteServerInTempDir() (server RunnerServer, call func()) {
966967
os.MkdirAll(themePath, 0755)
967968
os.WriteFile(filepath.Join(themePath, "simple.json"), []byte(simplePostman), 0755)
968969

970+
bindinPath := filepath.Join(dir, "data", "key-binding")
971+
os.MkdirAll(bindinPath, 0755)
972+
os.WriteFile(filepath.Join(bindinPath, "default.json"), binding, 0755)
973+
969974
writer := atest.NewFileWriter(dir)
970975
server = NewRemoteServer(writer, newLocalloaderFromStore(), nil, nil, dir, 1024*1024*4)
971976
return
@@ -992,6 +997,9 @@ var simpleTestCase string
992997
//go:embed testdata/postman.json
993998
var simplePostman string
994999

1000+
//go:embed testdata/keybinding.json
1001+
var binding []byte
1002+
9951003
const urlFoo = "http://foo"
9961004

9971005
type fakeServerStream struct {
@@ -1079,4 +1087,113 @@ func TestGetThemes(t *testing.T) {
10791087
})
10801088
assert.NoError(t, err)
10811089
assert.NotNil(t, theme)
1090+
1091+
theme, err = themeServer.GetTheme(context.Background(), &SimpleName{
1092+
Name: "not-exist",
1093+
})
1094+
assert.Error(t, err)
1095+
assert.False(t, theme.Success)
1096+
}
1097+
1098+
func TestKeybinding(t *testing.T) {
1099+
server, clean := getRemoteServerInTempDir()
1100+
defer clean()
1101+
1102+
themeServer, ok := server.(ThemeExtensionServer)
1103+
assert.True(t, ok)
1104+
1105+
reply, err := themeServer.GetBindings(context.Background(), &Empty{})
1106+
assert.NoError(t, err)
1107+
assert.Equal(t, 1, len(reply.Data))
1108+
1109+
var theme *CommonResult
1110+
theme, err = themeServer.GetBinding(context.Background(), &SimpleName{
1111+
Name: "default",
1112+
})
1113+
assert.NoError(t, err)
1114+
assert.NotNil(t, theme)
1115+
1116+
theme, err = themeServer.GetBinding(context.Background(), &SimpleName{
1117+
Name: "not-exist",
1118+
})
1119+
assert.Error(t, err)
1120+
assert.False(t, theme.Success)
1121+
}
1122+
1123+
func TestMockServer(t *testing.T) {
1124+
loader := mock.NewInMemoryServer(context.Background(), 0)
1125+
loader.SetupHandler(mock.NewInMemoryReader(""), "/")
1126+
mockServer := NewMockServerController(nil, loader, 0)
1127+
1128+
t.Run("reload as unsupported kind", func(t *testing.T) {
1129+
_, err := mockServer.Reload(context.Background(), &MockConfig{})
1130+
assert.Error(t, err)
1131+
})
1132+
1133+
t.Run("reload as memory kind", func(t *testing.T) {
1134+
_, err := mockServer.Reload(context.Background(), &MockConfig{
1135+
StoreKind: "memory",
1136+
})
1137+
assert.NoError(t, err)
1138+
})
1139+
1140+
t.Run("reload as localFile kind, but file is empty", func(t *testing.T) {
1141+
_, err := mockServer.Reload(context.Background(), &MockConfig{
1142+
StoreKind: "localFile",
1143+
})
1144+
assert.Error(t, err)
1145+
})
1146+
1147+
t.Run("reload as localFile kind, invalid file", func(t *testing.T) {
1148+
_, err := mockServer.Reload(context.Background(), &MockConfig{
1149+
StoreKind: "localFile",
1150+
StoreLocalFile: "testdata/simple.yaml",
1151+
})
1152+
assert.Error(t, err)
1153+
})
1154+
1155+
t.Run("reload as localFile kind", func(t *testing.T) {
1156+
_, err := mockServer.Reload(context.Background(), &MockConfig{
1157+
StoreKind: "localFile",
1158+
StoreLocalFile: "testdata/simple_mock.yaml",
1159+
})
1160+
assert.NoError(t, err)
1161+
1162+
config, err := mockServer.GetConfig(context.Background(), &Empty{})
1163+
assert.NoError(t, err)
1164+
assert.Equal(t, "localFile", config.StoreKind)
1165+
})
1166+
}
1167+
1168+
func TestUIExtension(t *testing.T) {
1169+
server, clean := getRemoteServerInTempDir()
1170+
defer clean()
1171+
1172+
uiServer, ok := server.(UIExtensionServer)
1173+
assert.True(t, ok)
1174+
assert.NotNil(t, uiServer)
1175+
1176+
ctx := context.Background()
1177+
menuList, err := uiServer.GetMenus(ctx, &Empty{})
1178+
assert.NoError(t, err)
1179+
assert.Equal(t, 0, len(menuList.Data))
1180+
1181+
result, _ := uiServer.GetPageOfJS(ctx, &SimpleName{Name: "name"})
1182+
assert.False(t, result.Success)
1183+
1184+
result, _ = uiServer.GetPageOfCSS(ctx, &SimpleName{Name: "name"})
1185+
assert.False(t, result.Success)
1186+
1187+
result, _ = uiServer.GetPageOfStatic(ctx, &SimpleName{Name: "name"})
1188+
assert.False(t, result.Success)
1189+
1190+
result, _ = uiServer.GetPageOfStatic(
1191+
context.WithValue(ctx, "X-Extension-Name", "fake"),
1192+
&SimpleName{Name: "name"})
1193+
assert.False(t, result.Success)
1194+
1195+
result, _ = uiServer.GetPageOfStatic(
1196+
context.WithValue(ctx, "X-Extension-Name", []string{"fake"}),
1197+
&SimpleName{Name: "name"})
1198+
assert.False(t, result.Success)
10821199
}

0 commit comments

Comments
 (0)