Files
xinmi/internal/application/repository/retriever/opensearch/transport_test.go
T
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

138 lines
4.8 KiB
Go

package opensearch
import (
"crypto/tls"
"errors"
"testing"
"github.com/Tencent/WeKnora/internal/types"
)
// TestNewOpenSearchClient_RejectsEmptyAddr verifies the constructor
// refuses an empty Addr — operators who forget to set OPENSEARCH_ADDR
// get a clear ErrConfigInvalid at registration instead of a cryptic
// transport error later.
func TestNewOpenSearchClient_RejectsEmptyAddr(t *testing.T) {
t.Parallel()
_, err := NewOpenSearchClient(&types.ConnectionConfig{Addr: ""})
if !errors.Is(err, ErrConfigInvalid) {
t.Fatalf("empty addr: want ErrConfigInvalid, got %v", err)
}
_, err = NewOpenSearchClient(nil)
if !errors.Is(err, ErrConfigInvalid) {
t.Fatalf("nil cfg: want ErrConfigInvalid, got %v", err)
}
}
// TestNewOpenSearchClient_Succeeds_OnValidAddr is a smoke test for the
// happy path — a non-empty Addr must produce a non-nil *osapi.Client.
// We don't probe the cluster (no network), just verify the constructor
// returns successfully.
func TestNewOpenSearchClient_Succeeds_OnValidAddr(t *testing.T) {
t.Parallel()
client, err := NewOpenSearchClient(&types.ConnectionConfig{
Addr: "https://opensearch.example.com:9200",
Username: "admin",
Password: "secret", // not a real password — wire-format only
})
if err != nil {
t.Fatalf("NewOpenSearchClient: %v", err)
}
if client == nil {
t.Fatal("client must be non-nil on success")
}
}
// TestBuildHTTPTransport_DefaultsToSecureTLS pins the default
// InsecureSkipVerify=false. Operators who don't opt in MUST get a
// strict-verify HTTPS client.
func TestBuildHTTPTransport_DefaultsToSecureTLS(t *testing.T) {
t.Parallel()
tr := buildHTTPTransport(false)
if tr.TLSClientConfig == nil {
t.Fatal("TLSClientConfig must be set")
}
if tr.TLSClientConfig.InsecureSkipVerify {
t.Fatal("default InsecureSkipVerify must be false (secure)")
}
}
// TestBuildHTTPTransport_InsecureSkipVerifyOptIn confirms the flag
// flows through to tls.Config. Self-signed dev clusters can opt in;
// production deployments MUST leave it false.
func TestBuildHTTPTransport_InsecureSkipVerifyOptIn(t *testing.T) {
t.Parallel()
tr := buildHTTPTransport(true)
if tr.TLSClientConfig == nil {
t.Fatal("TLSClientConfig must be set")
}
if !tr.TLSClientConfig.InsecureSkipVerify {
t.Fatal("InsecureSkipVerify=true must propagate to tls.Config")
}
}
// TestBuildHTTPTransport_TLS12MinVersion pins the TLS 1.2 hard floor.
// TLS 1.3 negotiates automatically when both ends support it; 1.0 / 1.1
// must be rejected at handshake time.
func TestBuildHTTPTransport_TLS12MinVersion(t *testing.T) {
t.Parallel()
tr := buildHTTPTransport(false)
if tr.TLSClientConfig.MinVersion != tls.VersionTLS12 {
t.Fatalf("MinVersion: want tls.VersionTLS12 (0x%x), got 0x%x",
tls.VersionTLS12, tr.TLSClientConfig.MinVersion)
}
}
// TestBuildHTTPTransport_CipherSuitesExcludeNonECDHE pins the explicit
// cipher list to forward-secrecy-only ECDHE suites. TLS 1.2's default
// list includes TLS_RSA_* (no forward secrecy); we drop them. TLS 1.3
// cipher selection is automatic so this only matters for the 1.2 path.
func TestBuildHTTPTransport_CipherSuitesExcludeNonECDHE(t *testing.T) {
t.Parallel()
tr := buildHTTPTransport(false)
cs := tr.TLSClientConfig.CipherSuites
if len(cs) == 0 {
t.Fatal("CipherSuites must be explicitly pinned (got empty)")
}
allowed := map[uint16]bool{
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: true,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: true,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: true,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: true,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: true,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305: true,
}
for _, suite := range cs {
if !allowed[suite] {
t.Fatalf("cipher suite 0x%x is not in the forward-secrecy whitelist", suite)
}
}
// Spot-check that a known-weak non-ECDHE cipher is NOT in the list.
for _, suite := range cs {
if suite == tls.TLS_RSA_WITH_AES_128_GCM_SHA256 {
t.Fatal("TLS_RSA_WITH_AES_128_GCM_SHA256 must NOT be present (no forward secrecy)")
}
}
}
// TestBuildHTTPTransport_PoolTuning pins the connection-pool settings.
// The Go default MaxIdleConnsPerHost is 2, which is too low for concurrent
// queries across multiple knowledge bases.
func TestBuildHTTPTransport_PoolTuning(t *testing.T) {
t.Parallel()
tr := buildHTTPTransport(false)
if tr.MaxIdleConnsPerHost < 32 {
t.Errorf("MaxIdleConnsPerHost: want >=32 for concurrent KB queries, got %d",
tr.MaxIdleConnsPerHost)
}
if tr.IdleConnTimeout == 0 {
t.Error("IdleConnTimeout must be set (Go default of 0 leaks idle conns)")
}
if tr.ResponseHeaderTimeout == 0 {
t.Error("ResponseHeaderTimeout must be set (Go default of 0 means unbounded)")
}
if tr.ExpectContinueTimeout == 0 {
t.Error("ExpectContinueTimeout must be set (avoids 100-continue stalls)")
}
}