mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-09 09:06:36 +08:00
feat(system): allow PublicConfigProvider to replace public config payload
This commit is contained in:
@@ -0,0 +1,12 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package contracts
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// PublicConfigProvider supplies the payload for GET /api/v1/config/public
|
||||||
|
// when a downstream plugin replaces Wavelet's default {configs, app} JSON.
|
||||||
|
type PublicConfigProvider interface {
|
||||||
|
PublicConfig(ctx context.Context) (any, error)
|
||||||
|
}
|
||||||
@@ -59,6 +59,16 @@ func (p *Plugin) Apply(ctx *core.Context) error {
|
|||||||
|
|
||||||
// 2. Public config
|
// 2. Public config
|
||||||
ctx.Router().GET("/api/v1/config/public", func(c *gin.Context) {
|
ctx.Router().GET("/api/v1/config/public", func(c *gin.Context) {
|
||||||
|
if p, err := core.Inject[contracts.PublicConfigProvider](ctx); err == nil && p != nil {
|
||||||
|
data, err := p.PublicConfig(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
logger.ErrorF(c.Request.Context(), "[System] public config provider failed: %v", err)
|
||||||
|
response.AbortInternal(c, "public config unavailable")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, response.OK(data))
|
||||||
|
return
|
||||||
|
}
|
||||||
configs, err := listPublicSystemConfigs(c.Request.Context(), ctx)
|
configs, err := listPublicSystemConfigs(c.Request.Context(), ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ErrorF(c.Request.Context(), "[System] query public system configs failed: %v", err)
|
logger.ErrorF(c.Request.Context(), "[System] query public system configs failed: %v", err)
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
// Copyright 2026 Arctel.net
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"Wavelet/core"
|
||||||
|
"Wavelet/core/contracts"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stubPublic struct{ payload any }
|
||||||
|
|
||||||
|
func (s stubPublic) PublicConfig(context.Context) (any, error) { return s.payload, nil }
|
||||||
|
|
||||||
|
type errPublic struct{ err error }
|
||||||
|
|
||||||
|
func (s errPublic) PublicConfig(context.Context) (any, error) { return nil, s.err }
|
||||||
|
|
||||||
|
func TestPublicConfigUsesProviderWhenPresent(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
ctx := core.NewContext(context.Background())
|
||||||
|
core.Provide[contracts.PublicConfigProvider](ctx, stubPublic{payload: map[string]string{"k": "v"}})
|
||||||
|
if err := New().Apply(ctx); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
body := invokePublicConfig(t, publicConfigHandler(t, ctx))
|
||||||
|
assertFlatKV(t, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicConfigUsesProviderRegisteredAfterApply(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
ctx := core.NewContext(context.Background())
|
||||||
|
if err := New().Apply(ctx); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
core.Provide[contracts.PublicConfigProvider](ctx, stubPublic{payload: map[string]string{"k": "v"}})
|
||||||
|
body := invokePublicConfig(t, publicConfigHandler(t, ctx))
|
||||||
|
assertFlatKV(t, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicConfigDefaultWithoutProvider(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
ctx := core.NewContext(context.Background())
|
||||||
|
if err := New().Apply(ctx); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
raw := invokePublicConfig(t, publicConfigHandler(t, ctx))
|
||||||
|
var data map[string]any
|
||||||
|
if err := json.Unmarshal(raw, &data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, ok := data["configs"]; !ok {
|
||||||
|
t.Fatalf("data = %s, want key configs", raw)
|
||||||
|
}
|
||||||
|
if _, ok := data["app"]; !ok {
|
||||||
|
t.Fatalf("data = %s, want key app", raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicConfigProviderErrorAbortsInternal(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
ctx := core.NewContext(context.Background())
|
||||||
|
core.Provide[contracts.PublicConfigProvider](ctx, errPublic{err: errors.New("secret boom")})
|
||||||
|
if err := New().Apply(ctx); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(w)
|
||||||
|
c.Request = httptest.NewRequest(http.MethodGet, "/api/v1/config/public", nil)
|
||||||
|
publicConfigHandler(t, ctx)(c)
|
||||||
|
|
||||||
|
if !c.IsAborted() {
|
||||||
|
t.Fatal("want request aborted on provider error")
|
||||||
|
}
|
||||||
|
if len(c.Errors) == 0 {
|
||||||
|
t.Fatal("want gin error on provider failure")
|
||||||
|
}
|
||||||
|
if got := c.Errors.Last().Error(); got != "public config unavailable" {
|
||||||
|
t.Fatalf("error = %q, want generic message", got)
|
||||||
|
}
|
||||||
|
if strings.Contains(w.Body.String(), "secret boom") {
|
||||||
|
t.Fatalf("leaked provider error: %s", w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func publicConfigHandler(t *testing.T, ctx *core.Context) gin.HandlerFunc {
|
||||||
|
t.Helper()
|
||||||
|
for _, rd := range ctx.Router().Routes() {
|
||||||
|
if rd.Method != "GET" || rd.Path != "/api/v1/config/public" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(rd.Handlers) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch h := rd.Handlers[0].(type) {
|
||||||
|
case gin.HandlerFunc:
|
||||||
|
return h
|
||||||
|
case func(*gin.Context):
|
||||||
|
return h
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected handler type %T", rd.Handlers[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatal("public config route missing")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokePublicConfig(t *testing.T, handler gin.HandlerFunc) json.RawMessage {
|
||||||
|
t.Helper()
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := gin.CreateTestContext(w)
|
||||||
|
c.Request = httptest.NewRequest(http.MethodGet, "/api/v1/config/public", nil)
|
||||||
|
handler(c)
|
||||||
|
var body struct {
|
||||||
|
Data json.RawMessage `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return body.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertFlatKV(t *testing.T, raw json.RawMessage) {
|
||||||
|
t.Helper()
|
||||||
|
var data map[string]any
|
||||||
|
if err := json.Unmarshal(raw, &data); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if data["k"] != "v" {
|
||||||
|
t.Fatalf("data = %s, want flat map", raw)
|
||||||
|
}
|
||||||
|
if _, ok := data["configs"]; ok {
|
||||||
|
t.Fatalf("data = %s, want provider payload without default configs", raw)
|
||||||
|
}
|
||||||
|
if _, ok := data["app"]; ok {
|
||||||
|
t.Fatalf("data = %s, want provider payload without default app", raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user