mengstack-api/internal/kernel/plugin/sandbox_test.go
MengStack Dev df809f1045
Some checks failed
CI / Build & Test (push) Failing after 1m31s
feat: user CRUD API + dashboard stats + kernel infrastructure
- Add user management endpoints (list/create/get/update/delete) with pagination and search
- Add dashboard stats endpoint with tenant/user/online counts and growth metrics
- Add tenant resolver middleware for multi-tenant request scoping
- Add i18n kernel with zh/en message files and AcceptLanguage middleware
- Add WebSocket hub/handler for real-time communication
- Add job scheduler kernel with cron support
- Add plugin sandbox for isolated execution
- Add storage kernel (local filesystem)
- Add event bus kernel for pub/sub
- Add cache kernel abstraction
- Add database migration runner and version upgrade checker
- Add rate limiting middleware with Redis backend
- Add SQL migrations for rbac, audit_logs, settings, notifications, examples
- Extend user repository with list/delete/count operations
- Register all module routes with tenant resolver
2026-10-03 03:42:58 +08:00

192 lines
4.9 KiB
Go

package plugin
import (
"io/fs"
"testing"
"github.com/gin-gonic/gin"
"go.uber.org/fx"
"go.uber.org/zap"
"mengstack/internal/kernel/tenant"
)
func TestSandbox_CheckDatabase(t *testing.T) {
log := zap.NewNop()
sb := NewSandbox(log)
sb.Register("test-plugin", Permissions{
Database: []string{"users", "posts"},
})
tests := []struct {
plugin string
table string
wantOK bool
}{
{"test-plugin", "users", true},
{"test-plugin", "posts", true},
{"test-plugin", "orders", false},
{"unknown-plugin", "users", false},
}
for _, tt := range tests {
err := sb.CheckDatabase(tt.plugin, tt.table)
if tt.wantOK && err != nil {
t.Errorf("CheckDatabase(%q, %q) unexpected error: %v", tt.plugin, tt.table, err)
}
if !tt.wantOK && err == nil {
t.Errorf("CheckDatabase(%q, %q) expected error, got nil", tt.plugin, tt.table)
}
}
}
func TestSandbox_CheckDatabaseWildcard(t *testing.T) {
log := zap.NewNop()
sb := NewSandbox(log)
sb.Register("all-access", Permissions{
Database: []string{""},
})
if err := sb.CheckDatabase("all-access", "any_table"); err != nil {
t.Errorf("wildcard database access should be allowed, got: %v", err)
}
}
func TestSandbox_CheckStorage(t *testing.T) {
log := zap.NewNop()
sb := NewSandbox(log)
sb.Register("with-storage", Permissions{Storage: true})
sb.Register("no-storage", Permissions{Storage: false})
if err := sb.CheckStorage("with-storage"); err != nil {
t.Errorf("expected storage access allowed, got: %v", err)
}
if err := sb.CheckStorage("no-storage"); err == nil {
t.Error("expected storage access denied")
}
}
func TestSandbox_CheckNetwork(t *testing.T) {
log := zap.NewNop()
sb := NewSandbox(log)
sb.Register("net-plugin", Permissions{
Network: []string{"api.example.com"},
})
if err := sb.CheckNetwork("net-plugin", "api.example.com"); err != nil {
t.Errorf("expected network access allowed, got: %v", err)
}
if err := sb.CheckNetwork("net-plugin", "evil.com"); err == nil {
t.Error("expected network access denied for unauthorized domain")
}
}
func TestSandbox_CheckEvent(t *testing.T) {
log := zap.NewNop()
sb := NewSandbox(log)
sb.Register("event-plugin", Permissions{
Events: []string{"user.created", "user.deleted"},
})
if err := sb.CheckEvent("event-plugin", "user.created"); err != nil {
t.Errorf("expected event allowed, got: %v", err)
}
if err := sb.CheckEvent("event-plugin", "order.created"); err == nil {
t.Error("expected event denied for unauthorized event")
}
}
func TestSandbox_CheckEventWildcard(t *testing.T) {
log := zap.NewNop()
sb := NewSandbox(log)
sb.Register("wildcard-events", Permissions{
Events: []string{"*"},
})
if err := sb.CheckEvent("wildcard-events", "anything"); err != nil {
t.Errorf("wildcard event access should be allowed, got: %v", err)
}
}
func TestSandbox_CheckRoute(t *testing.T) {
log := zap.NewNop()
sb := NewSandbox(log)
sb.Register("route-plugin", Permissions{
Routes: []string{"/api/v1/items", "/api/v1/items/*"},
})
if err := sb.CheckRoute("route-plugin", "/api/v1/items"); err != nil {
t.Errorf("expected route allowed, got: %v", err)
}
if err := sb.CheckRoute("route-plugin", "/api/v1/items/123"); err != nil {
t.Errorf("expected wildcard route allowed, got: %v", err)
}
if err := sb.CheckRoute("route-plugin", "/api/v1/users"); err == nil {
t.Error("expected route denied for unauthorized path")
}
}
func TestSandbox_CheckAdminPanel(t *testing.T) {
log := zap.NewNop()
sb := NewSandbox(log)
sb.Register("admin-plugin", Permissions{AdminPanel: true})
sb.Register("no-admin", Permissions{AdminPanel: false})
if err := sb.CheckAdminPanel("admin-plugin"); err != nil {
t.Errorf("expected admin access allowed, got: %v", err)
}
if err := sb.CheckAdminPanel("no-admin"); err == nil {
t.Error("expected admin access denied")
}
}
func TestSandbox_SafeInit_PanicRecovery(t *testing.T) {
log := zap.NewNop()
sb := NewSandbox(log)
panicPlugin := &mockPlugin{
name: "panic-plugin",
initFn: func() error {
panic("something went wrong")
},
}
err := sb.SafeInit(panicPlugin)
if err == nil {
t.Error("expected error from panicked init")
}
}
func TestSandbox_SafeInit_Success(t *testing.T) {
log := zap.NewNop()
sb := NewSandbox(log)
okPlugin := &mockPlugin{
name: "ok-plugin",
initFn: func() error { return nil },
}
if err := sb.SafeInit(okPlugin); err != nil {
t.Errorf("unexpected error: %v", err)
}
}
type mockPlugin struct {
name string
initFn func() error
}
func (m *mockPlugin) Metadata() Metadata { return Metadata{Name: m.name, Version: "0.0.1", Description: "mock"} }
func (m *mockPlugin) Permissions() Permissions { return Permissions{} }
func (m *mockPlugin) FxOption() fx.Option { return fx.Options() }
func (m *mockPlugin) SetupRoutes(_ *gin.Engine, _ gin.HandlerFunc, _ tenant.Resolver) {}
func (m *mockPlugin) Init() error { return m.initFn() }
func (m *mockPlugin) MigrationsFS() fs.FS { return nil }