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
169 lines
5.7 KiB
Go
169 lines
5.7 KiB
Go
package im
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"mime/multipart"
|
|
"testing"
|
|
|
|
"github.com/Tencent/WeKnora/internal/types"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// stubIMFileService implements interfaces.FileService for IM resolver tests.
|
|
type stubIMFileService struct {
|
|
getFileURL func(ctx context.Context, filePath string) (string, error)
|
|
}
|
|
|
|
func (s *stubIMFileService) CheckConnectivity(context.Context) error { return nil }
|
|
|
|
func (s *stubIMFileService) SaveFile(context.Context, *multipart.FileHeader, uint64, string) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (s *stubIMFileService) SaveBytes(context.Context, []byte, uint64, string, bool) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (s *stubIMFileService) GetFile(context.Context, string) (io.ReadCloser, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *stubIMFileService) GetFileURL(ctx context.Context, filePath string) (string, error) {
|
|
if s.getFileURL != nil {
|
|
return s.getFileURL(ctx, filePath)
|
|
}
|
|
return "https://global-storage.example/" + filePath, nil
|
|
}
|
|
|
|
func (s *stubIMFileService) DeleteFile(context.Context, string) error { return nil }
|
|
|
|
func (s *stubIMFileService) CopyFile(context.Context, string, uint64, string) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func TestBuildIMFileServiceForProvider_FallbackToGlobal(t *testing.T) {
|
|
stub := &stubIMFileService{}
|
|
tenant := &types.Tenant{
|
|
StorageEngineConfig: &types.StorageEngineConfig{
|
|
DefaultProvider: "cos",
|
|
COS: &types.COSEngineConfig{
|
|
SecretID: "id",
|
|
SecretKey: "key",
|
|
BucketName: "bucket",
|
|
Region: "ap-shanghai",
|
|
},
|
|
},
|
|
}
|
|
|
|
svc := buildIMFileServiceForProvider(tenant, "minio", stub)
|
|
require.NotNil(t, svc)
|
|
got, err := svc.GetFileURL(context.Background(), "minio://wizard-test/10000/exports/a.png")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "https://global-storage.example/minio://wizard-test/10000/exports/a.png", got)
|
|
}
|
|
|
|
func TestIMFileServiceResolver_CachesPerProvider(t *testing.T) {
|
|
stub := &stubIMFileService{}
|
|
tenant := &types.Tenant{
|
|
StorageEngineConfig: &types.StorageEngineConfig{
|
|
DefaultProvider: "cos",
|
|
COS: &types.COSEngineConfig{
|
|
SecretID: "id",
|
|
SecretKey: "key",
|
|
BucketName: "bucket",
|
|
Region: "ap-shanghai",
|
|
},
|
|
},
|
|
}
|
|
r := newIMFileServiceResolver(tenant, stub)
|
|
|
|
svc1 := r.ResolveFileService("minio://wizard-test/10000/a.png")
|
|
svc2 := r.ResolveFileService("minio://wizard-test/10000/b.png")
|
|
assert.Same(t, svc1, svc2, "same provider should reuse cached FileService")
|
|
|
|
svc3 := r.ResolveFileService("local://10000/c.png")
|
|
assert.NotSame(t, svc1, svc3, "different provider should use a different service")
|
|
}
|
|
|
|
func TestRewriteStorageURLs_MinIOFallbackViaGlobal(t *testing.T) {
|
|
stub := &stubIMFileService{
|
|
getFileURL: func(_ context.Context, filePath string) (string, error) {
|
|
return "https://minio.example/presigned?path=" + filePath, nil
|
|
},
|
|
}
|
|
tenant := &types.Tenant{
|
|
StorageEngineConfig: &types.StorageEngineConfig{
|
|
DefaultProvider: "cos",
|
|
COS: &types.COSEngineConfig{
|
|
SecretID: "id",
|
|
SecretKey: "key",
|
|
BucketName: "bucket",
|
|
Region: "ap-shanghai",
|
|
},
|
|
},
|
|
}
|
|
in := ``
|
|
resolver := newIMFileServiceResolver(tenant, stub)
|
|
out := rewriteStorageURLs(context.Background(), in, resolver)
|
|
assert.Contains(t, out, "https://minio.example/presigned")
|
|
assert.NotContains(t, out, "](minio://")
|
|
}
|
|
|
|
func TestRewriteStorageURLs_ScopedPath(t *testing.T) {
|
|
stub := &stubIMFileService{
|
|
getFileURL: func(_ context.Context, filePath string) (string, error) {
|
|
assert.Equal(t, "storage://backend-a/cos://bucket/ap-test/10000/exports/a.png", filePath)
|
|
return "https://storage.example/a.png", nil
|
|
},
|
|
}
|
|
input := ""
|
|
output := rewriteStorageURLs(context.Background(), input, newIMFileServiceResolver(&types.Tenant{}, stub))
|
|
assert.Contains(t, output, "https://storage.example/a.png")
|
|
}
|
|
|
|
// When GetFileURL resolves a resource:// alias to a still-internal storage://
|
|
// path (no public HTTP URL), the rewrite must be a no-op rather than emit the
|
|
// unrenderable URL to the IM client.
|
|
func TestRewriteStorageURLs_NonHTTPResultIsNoOp(t *testing.T) {
|
|
stub := &stubIMFileService{
|
|
getFileURL: func(_ context.Context, _ string) (string, error) {
|
|
return "storage://7cb970a6/oss://bcjy/10000/exports/a.png", nil
|
|
},
|
|
}
|
|
in := ""
|
|
out := rewriteStorageURLs(context.Background(), in, newIMFileServiceResolver(&types.Tenant{}, stub))
|
|
assert.Equal(t, in, out)
|
|
assert.NotContains(t, out, "storage://")
|
|
}
|
|
|
|
// URL schemes are case-insensitive (RFC 3986); an uppercase-scheme result (e.g.
|
|
// from an OBS_PROXY_DOMAIN configured as HTTPS://...) is renderable and must be
|
|
// substituted, not dropped as "non-HTTP".
|
|
func TestRewriteStorageURLs_UppercaseSchemeIsSubstituted(t *testing.T) {
|
|
stub := &stubIMFileService{
|
|
getFileURL: func(_ context.Context, _ string) (string, error) {
|
|
return "HTTPS://cdn.example.com/x.png", nil
|
|
},
|
|
}
|
|
in := ""
|
|
out := rewriteStorageURLs(context.Background(), in, newIMFileServiceResolver(&types.Tenant{}, stub))
|
|
assert.Contains(t, out, "HTTPS://cdn.example.com/x.png")
|
|
assert.NotContains(t, out, "resource://")
|
|
}
|
|
|
|
func TestCleanIMContent_MinIOFallbackIntegration(t *testing.T) {
|
|
stub := &stubIMFileService{
|
|
getFileURL: func(_ context.Context, _ string) (string, error) {
|
|
return "https://minio.example/img.png", nil
|
|
},
|
|
}
|
|
tenant := &types.Tenant{
|
|
StorageEngineConfig: &types.StorageEngineConfig{DefaultProvider: "cos"},
|
|
}
|
|
in := "see  ok"
|
|
out := cleanIMContent(context.Background(), in, tenant, stub)
|
|
assert.Contains(t, out, "https://minio.example/img.png")
|
|
}
|