Files
yawan f7fc992f57
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
WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
2026-09-22 16:19:06 +08:00

154 lines
4.4 KiB
Go

package opensearch
import (
"context"
"strings"
"sync"
"testing"
"github.com/Tencent/WeKnora/internal/types"
)
// spySink records audit events for assertions.
type spySink struct {
mu sync.Mutex
indexCreated []indexCreatedEvent
reindex []reindexEvent
}
type indexCreatedEvent struct {
alias string
dim int
}
type reindexEvent struct {
src, dst string
docs int64
}
func (s *spySink) EmitIndexCreated(_ context.Context, alias string, dim int) {
s.mu.Lock()
defer s.mu.Unlock()
s.indexCreated = append(s.indexCreated, indexCreatedEvent{alias, dim})
}
func (s *spySink) EmitReindexExecuted(_ context.Context, src, dst string, docs int64) {
s.mu.Lock()
defer s.mu.Unlock()
s.reindex = append(s.reindex, reindexEvent{src, dst, docs})
}
// TestBuildInternalCfg_ReadsHNSWFields verifies the HNSW IndexConfig fields
// flow into internalCfg, falling back to defaults when unset.
func TestBuildInternalCfg_ReadsHNSWFields(t *testing.T) {
t.Run("reads set fields", func(t *testing.T) {
cfg, err := buildInternalCfg(&types.IndexConfig{
HNSWM: 24,
HNSWEFConstruction: 200,
HNSWEFSearch: 128,
KNNEngine: "faiss",
NumberOfShards: 2,
})
if err != nil {
t.Fatal(err)
}
if cfg.hnswM != 24 || cfg.hnswEFConstruction != 200 || cfg.efSearch != 128 || cfg.knnEngine != "faiss" {
t.Errorf("HNSW not wired: %+v", cfg)
}
if cfg.shards != 2 {
t.Errorf("shards: want 2, got %d", cfg.shards)
}
})
t.Run("defaults when unset", func(t *testing.T) {
cfg, err := buildInternalCfg(&types.IndexConfig{})
if err != nil {
t.Fatal(err)
}
if cfg.hnswM != 16 || cfg.hnswEFConstruction != 100 || cfg.efSearch != 100 || cfg.knnEngine != "lucene" {
t.Errorf("defaults not applied: %+v", cfg)
}
})
t.Run("nil config is all defaults", func(t *testing.T) {
cfg, err := buildInternalCfg(nil)
if err != nil {
t.Fatal(err)
}
if cfg.knnEngine != "lucene" || cfg.hnswM != 16 {
t.Errorf("nil defaults wrong: %+v", cfg)
}
})
}
// TestBuildIndexMapping_ReflectsHNSWConfig verifies the end-to-end path
// IndexConfig → buildInternalCfg → buildIndexMapping carries the operator's
// HNSW values into the cluster mapping JSON (regression guard for the wire-
// through, since defaults coincide with common values).
func TestBuildIndexMapping_ReflectsHNSWConfig(t *testing.T) {
cfg, err := buildInternalCfg(&types.IndexConfig{
HNSWM: 24,
HNSWEFConstruction: 200,
HNSWEFSearch: 128,
KNNEngine: "faiss",
})
if err != nil {
t.Fatal(err)
}
body, err := buildIndexMapping(cfg, 768)
if err != nil {
t.Fatal(err)
}
s := string(body)
for _, want := range []string{`"m":24`, `"ef_construction":200`, `"engine":"faiss"`, `"knn.algo_param.ef_search":128`} {
if !strings.Contains(s, want) {
t.Errorf("mapping JSON missing %q\n%s", want, s)
}
}
}
// TestWithAuditSink_SetsAndNilSafe verifies the functional option.
func TestWithAuditSink_SetsAndNilSafe(t *testing.T) {
spy := &spySink{}
r := &Repository{}
WithAuditSink(spy)(r)
if r.sink != spy {
t.Fatal("WithAuditSink did not set the sink")
}
// nil must not clobber an already-set sink
WithAuditSink(nil)(r)
if r.sink != spy {
t.Fatal("WithAuditSink(nil) clobbered the sink")
}
}
// TestAuditSink_NopByDefault verifies a Repository with no sink does not panic
// when the audit accessor is used (nopSink fallback).
func TestAuditSink_NopByDefault(t *testing.T) {
var _ AuditSink = nopSink{} // compile-time assertion
r := &Repository{} // sink left nil
// Must not panic.
r.auditSink().EmitIndexCreated(context.Background(), "weknora_768", 768)
r.auditSink().EmitReindexExecuted(context.Background(), "a", "b", 3)
}
// TestAuditSink_EmitIndexCreated_OnEnsureReady verifies createIndexAndAlias
// emits exactly one index-created event with the per-dim alias when a new
// index is provisioned.
func TestAuditSink_EmitIndexCreated_OnEnsureReady(t *testing.T) {
repo, ts := newTestRepo(t, (&indexLifecycleHandler{}).ServeHTTP)
defer ts.Close()
spy := &spySink{}
repo.sink = spy
if err := repo.ensureReady(context.Background(), 768); err != nil {
t.Fatalf("ensureReady: %v", err)
}
if len(spy.indexCreated) != 1 {
t.Fatalf("want 1 index_created event, got %d", len(spy.indexCreated))
}
got := spy.indexCreated[0]
if got.alias != "weknora_test_768" || got.dim != 768 {
t.Errorf("event mismatch: %+v", got)
}
}