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,137 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSignFileURL_RoundTrip(t *testing.T) {
|
||||
t.Setenv("SYSTEM_AES_KEY", "weknora-test-aes-key-32bytes!!!")
|
||||
|
||||
baseURL := "https://weknora.example.com"
|
||||
filePath := "local://1/abc/img.png"
|
||||
var tenantID uint64 = 1
|
||||
|
||||
signed, err := SignFileURL(baseURL, filePath, tenantID, 1*time.Hour)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, signed, "https://weknora.example.com/api/v1/files/presigned")
|
||||
assert.Contains(t, signed, "file_path=")
|
||||
assert.Contains(t, signed, "tenant_id=1")
|
||||
assert.Contains(t, signed, "sig=")
|
||||
}
|
||||
|
||||
func TestSignFileURL_NoKey(t *testing.T) {
|
||||
t.Setenv("SYSTEM_AES_KEY", "")
|
||||
|
||||
_, err := SignFileURL("https://example.com", "local://1/img.png", 1, 0)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "SYSTEM_AES_KEY")
|
||||
}
|
||||
|
||||
func TestVerifyFileURLSig_Valid(t *testing.T) {
|
||||
t.Setenv("SYSTEM_AES_KEY", "weknora-test-aes-key-32bytes!!!")
|
||||
|
||||
filePath := "local://42/knowledge/img.jpg"
|
||||
var tenantID uint64 = 42
|
||||
key := getPresignKey()
|
||||
require.NotNil(t, key)
|
||||
|
||||
expires := time.Now().Add(1 * time.Hour).Unix()
|
||||
sig := signPayload(key, filePath, tenantID, expires)
|
||||
|
||||
assert.True(t, VerifyFileURLSig(filePath, tenantID, strconv.FormatInt(expires, 10), sig))
|
||||
}
|
||||
|
||||
func TestVerifyFileURLSig_Expired(t *testing.T) {
|
||||
t.Setenv("SYSTEM_AES_KEY", "weknora-test-aes-key-32bytes!!!")
|
||||
|
||||
filePath := "local://1/img.png"
|
||||
var tenantID uint64 = 1
|
||||
key := getPresignKey()
|
||||
require.NotNil(t, key)
|
||||
|
||||
expires := time.Now().Add(-1 * time.Hour).Unix() // already expired
|
||||
sig := signPayload(key, filePath, tenantID, expires)
|
||||
|
||||
assert.False(t, VerifyFileURLSig(filePath, tenantID, strconv.FormatInt(expires, 10), sig))
|
||||
}
|
||||
|
||||
func TestVerifyFileURLSig_Tampered(t *testing.T) {
|
||||
t.Setenv("SYSTEM_AES_KEY", "weknora-test-aes-key-32bytes!!!")
|
||||
|
||||
filePath := "local://1/img.png"
|
||||
var tenantID uint64 = 1
|
||||
key := getPresignKey()
|
||||
require.NotNil(t, key)
|
||||
|
||||
expires := time.Now().Add(1 * time.Hour).Unix()
|
||||
sig := signPayload(key, filePath, tenantID, expires)
|
||||
expiresStr := strconv.FormatInt(expires, 10)
|
||||
|
||||
// Tamper with file path
|
||||
assert.False(t, VerifyFileURLSig("local://1/other.png", tenantID, expiresStr, sig))
|
||||
// Tamper with tenant ID
|
||||
assert.False(t, VerifyFileURLSig(filePath, 999, expiresStr, sig))
|
||||
// Tamper with signature
|
||||
assert.False(t, VerifyFileURLSig(filePath, tenantID, expiresStr, "deadbeef"))
|
||||
}
|
||||
|
||||
func TestVerifyFileURLSig_NoKey(t *testing.T) {
|
||||
t.Setenv("SYSTEM_AES_KEY", "")
|
||||
|
||||
assert.False(t, VerifyFileURLSig("local://1/img.png", 1, "99999999999", "abc"))
|
||||
}
|
||||
|
||||
func TestValidateStoragePathTenant(t *testing.T) {
|
||||
assert.NoError(t, ValidateStoragePathTenant("local://42/knowledge/file.pdf", 42))
|
||||
assert.Error(t, ValidateStoragePathTenant("local://7/knowledge/file.pdf", 42))
|
||||
assert.Error(t, ValidateStoragePathTenant("local://docs/example.txt", 42))
|
||||
}
|
||||
|
||||
func TestValidateKBScopedStoragePath(t *testing.T) {
|
||||
const tenantID uint64 = 10008
|
||||
|
||||
assert.NoError(t, ValidateKBScopedStoragePath("local://10008/exports/img.jpg", tenantID))
|
||||
assert.NoError(t, ValidateKBScopedStoragePath("minio://bucket/10008/exports/uuid.png", tenantID))
|
||||
assert.NoError(t, ValidateKBScopedStoragePath("oss://bucket/exports/10008/uuid.png", tenantID))
|
||||
|
||||
// storage://<backendID>/ wrapped paths must resolve to the same tenant/exports scope.
|
||||
assert.NoError(t, ValidateKBScopedStoragePath("storage://backend-a/local://10008/exports/img.jpg", tenantID))
|
||||
assert.NoError(t, ValidateKBScopedStoragePath("storage://backend-a/cos://bucket/ap-test/10008/exports/a.png", tenantID))
|
||||
|
||||
assert.Error(t, ValidateKBScopedStoragePath("local://10008/knowledge-id/123.pdf", tenantID))
|
||||
assert.Error(t, ValidateKBScopedStoragePath("local://9999/exports/img.jpg", tenantID))
|
||||
assert.Error(t, ValidateKBScopedStoragePath("local://10008/other/img.jpg", tenantID))
|
||||
assert.Error(t, ValidateKBScopedStoragePath("storage://backend-a/local://9999/exports/img.jpg", tenantID))
|
||||
}
|
||||
|
||||
func TestParseTenantIDFromStoragePath(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
want uint64
|
||||
}{
|
||||
{"local://1/abc/img.png", 1},
|
||||
{"local://42/knowledge/file.pdf", 42},
|
||||
{"minio://bucket/1/abc/img.png", 1},
|
||||
{"s3://bucket/weknora/1/abc/img.png", 1},
|
||||
{"cos://bucket/region/prefix/1/abc/img.png", 1},
|
||||
{"tos://bucket/1/abc/img.png", 1},
|
||||
{"oss://bucket/weknora/1/abc/img.png", 1},
|
||||
{"https://example.com/img.png", 0},
|
||||
{"invalid", 0},
|
||||
{"local://exports/file.csv", 0},
|
||||
{"storage://backend-a/local://1/abc/img.png", 1},
|
||||
{"storage://backend-a/cos://bucket/region/prefix/42/abc/img.png", 42},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.path, func(t *testing.T) {
|
||||
got := ParseTenantIDFromStoragePath(tt.path)
|
||||
assert.Equal(t, tt.want, got, "ParseTenantIDFromStoragePath(%q)", tt.path)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user