WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
apprepo "github.com/Tencent/WeKnora/internal/application/repository"
|
||||
"github.com/Tencent/WeKnora/internal/config"
|
||||
"github.com/Tencent/WeKnora/internal/handler"
|
||||
"github.com/Tencent/WeKnora/internal/middleware"
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type stubWikiKBLookup struct {
|
||||
kbs map[string]*types.KnowledgeBase
|
||||
}
|
||||
|
||||
func (s *stubWikiKBLookup) GetKnowledgeBaseByID(_ context.Context, id string) (*types.KnowledgeBase, error) {
|
||||
if kb, ok := s.kbs[id]; ok {
|
||||
return kb, nil
|
||||
}
|
||||
return nil, apprepo.ErrKnowledgeBaseNotFound
|
||||
}
|
||||
|
||||
func newWikiRouteTestEngine(t *testing.T, callerTenantID uint64, kbLookup *stubWikiKBLookup) *gin.Engine {
|
||||
return newKBRouteTestEngine(t, callerTenantID, kbLookup, nil, func(r *gin.RouterGroup, guards *rbacGuards) {
|
||||
RegisterWikiPageRoutes(r, &handler.WikiPageHandler{}, guards)
|
||||
})
|
||||
}
|
||||
|
||||
func newInitializationRouteTestEngine(t *testing.T, callerTenantID uint64, kbLookup *stubWikiKBLookup) *gin.Engine {
|
||||
return newKBRouteTestEngine(t, callerTenantID, kbLookup, nil, func(r *gin.RouterGroup, guards *rbacGuards) {
|
||||
RegisterInitializationRoutes(r, &handler.InitializationHandler{}, guards)
|
||||
})
|
||||
}
|
||||
|
||||
func newKBRouteTestEngine(
|
||||
t *testing.T,
|
||||
callerTenantID uint64,
|
||||
kbLookup *stubWikiKBLookup,
|
||||
apiKeyScope *types.TenantAPIKeyScope,
|
||||
register func(r *gin.RouterGroup, guards *rbacGuards),
|
||||
) *gin.Engine {
|
||||
t.Helper()
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
enabled := true
|
||||
cfg := &config.Config{
|
||||
Tenant: &config.TenantConfig{EnableRBAC: &enabled},
|
||||
}
|
||||
guards := &rbacGuards{
|
||||
cfg: cfg,
|
||||
kbService: kbLookup,
|
||||
}
|
||||
|
||||
r := gin.New()
|
||||
r.Use(middleware.ErrorHandler())
|
||||
r.Use(func(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
ctx = context.WithValue(ctx, types.TenantIDContextKey, callerTenantID)
|
||||
role := types.TenantRoleViewer
|
||||
if apiKeyScope != nil {
|
||||
ctx = types.WithTenantAPIKeyScope(ctx, *apiKeyScope)
|
||||
if apiKeyScope.FullAccess {
|
||||
role = types.TenantRoleOwner
|
||||
}
|
||||
}
|
||||
ctx = context.WithValue(ctx, types.TenantRoleContextKey, role)
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
c.Set(types.TenantIDContextKey.String(), callerTenantID)
|
||||
c.Next()
|
||||
})
|
||||
|
||||
register(r.Group("/api/v1"), guards)
|
||||
return r
|
||||
}
|
||||
|
||||
func tenantKBLookupFixture() *stubWikiKBLookup {
|
||||
return &stubWikiKBLookup{
|
||||
kbs: map[string]*types.KnowledgeBase{
|
||||
"kb-allowed": {ID: "kb-allowed", TenantID: 1, Type: types.KnowledgeBaseTypeWiki},
|
||||
"kb-other": {ID: "kb-other", TenantID: 1, Type: types.KnowledgeBaseTypeWiki},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitializationConfigRouteDenyCrossTenantKB(t *testing.T) {
|
||||
kbLookup := &stubWikiKBLookup{
|
||||
kbs: map[string]*types.KnowledgeBase{
|
||||
"kb-victim": {ID: "kb-victim", TenantID: 999},
|
||||
},
|
||||
}
|
||||
engine := newInitializationRouteTestEngine(t, 1, kbLookup)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/initialization/config/kb-victim", nil)
|
||||
engine.ServeHTTP(rec, req)
|
||||
require.Equal(t, http.StatusForbidden, rec.Code, "body=%s", rec.Body.String())
|
||||
}
|
||||
|
||||
func TestWikiReadRoutesDenyCrossTenantKB(t *testing.T) {
|
||||
kbLookup := &stubWikiKBLookup{
|
||||
kbs: map[string]*types.KnowledgeBase{
|
||||
"kb-victim": {
|
||||
ID: "kb-victim",
|
||||
TenantID: 999,
|
||||
Type: types.KnowledgeBaseTypeWiki,
|
||||
},
|
||||
},
|
||||
}
|
||||
engine := newWikiRouteTestEngine(t, 1, kbLookup)
|
||||
|
||||
paths := []string{
|
||||
"/api/v1/knowledgebase/kb-victim/wiki/pages",
|
||||
"/api/v1/knowledgebase/kb-victim/wiki/pages/secret-page",
|
||||
"/api/v1/knowledgebase/kb-victim/wiki/folders",
|
||||
"/api/v1/knowledgebase/kb-victim/wiki/index",
|
||||
"/api/v1/knowledgebase/kb-victim/wiki/graph",
|
||||
"/api/v1/knowledgebase/kb-victim/wiki/stats",
|
||||
"/api/v1/knowledgebase/kb-victim/wiki/search?q=test",
|
||||
"/api/v1/knowledgebase/kb-victim/wiki/lint",
|
||||
"/api/v1/knowledgebase/kb-victim/wiki/issues",
|
||||
"/api/v1/knowledgebase/kb-victim/wiki/revisions/secret-page",
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||
engine.ServeHTTP(rec, req)
|
||||
require.Equal(t, http.StatusForbidden, rec.Code, "body=%s", rec.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiOperationLogRouteIsRemoved(t *testing.T) {
|
||||
engine := newWikiRouteTestEngine(t, 1, tenantKBLookupFixture())
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/knowledgebase/kb-allowed/wiki/log", nil)
|
||||
engine.ServeHTTP(rec, req)
|
||||
require.Equal(t, http.StatusNotFound, rec.Code, "body=%s", rec.Body.String())
|
||||
}
|
||||
|
||||
func TestInitializationWriteRoutesDenyOutOfScopeAPIKeyKB(t *testing.T) {
|
||||
kbLookup := tenantKBLookupFixture()
|
||||
scope := &types.TenantAPIKeyScope{
|
||||
KnowledgeBaseIDs: types.StringArray{"kb-allowed"},
|
||||
Capabilities: types.StringArray{string(types.APIKeyCapabilityManageKnowledgeBases)},
|
||||
}
|
||||
engine := newKBRouteTestEngine(t, 1, kbLookup, scope, func(r *gin.RouterGroup, guards *rbacGuards) {
|
||||
RegisterInitializationRoutes(r, &handler.InitializationHandler{}, guards)
|
||||
})
|
||||
|
||||
cases := []struct {
|
||||
method string
|
||||
path string
|
||||
body string
|
||||
}{
|
||||
{http.MethodPut, "/api/v1/initialization/config/kb-other", `{}`},
|
||||
{http.MethodPost, "/api/v1/initialization/initialize/kb-other", `{}`},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.method+" "+tc.path, func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(tc.method, tc.path, strings.NewReader(tc.body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
engine.ServeHTTP(rec, req)
|
||||
require.Equal(t, http.StatusForbidden, rec.Code, "body=%s", rec.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiWriteRoutesDenyOutOfScopeAPIKeyKB(t *testing.T) {
|
||||
kbLookup := tenantKBLookupFixture()
|
||||
scope := &types.TenantAPIKeyScope{
|
||||
KnowledgeBaseIDs: types.StringArray{"kb-allowed"},
|
||||
Capabilities: types.StringArray{string(types.APIKeyCapabilityIngest)},
|
||||
}
|
||||
engine := newKBRouteTestEngine(t, 1, kbLookup, scope, func(r *gin.RouterGroup, guards *rbacGuards) {
|
||||
RegisterWikiPageRoutes(r, &handler.WikiPageHandler{}, guards)
|
||||
})
|
||||
|
||||
cases := []struct {
|
||||
method string
|
||||
path string
|
||||
}{
|
||||
{http.MethodPost, "/api/v1/knowledgebase/kb-other/wiki/pages"},
|
||||
{http.MethodPut, "/api/v1/knowledgebase/kb-other/wiki/pages/some-page"},
|
||||
{http.MethodDelete, "/api/v1/knowledgebase/kb-other/wiki/pages/some-page"},
|
||||
{http.MethodPost, "/api/v1/knowledgebase/kb-other/wiki/folders"},
|
||||
{http.MethodPost, "/api/v1/knowledgebase/kb-other/wiki/rebuild-links"},
|
||||
{http.MethodPost, "/api/v1/knowledgebase/kb-other/wiki/auto-fix"},
|
||||
{http.MethodPut, "/api/v1/knowledgebase/kb-other/wiki/issues/1/status"},
|
||||
{http.MethodPost, "/api/v1/knowledgebase/kb-other/wiki/revert"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.method+" "+tc.path, func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(tc.method, tc.path, strings.NewReader(`{}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
engine.ServeHTTP(rec, req)
|
||||
require.Equal(t, http.StatusForbidden, rec.Code, "body=%s", rec.Body.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user