Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

367 lines
11 KiB
Go

package duckdb
import (
"errors"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.kenn.io/agentsview/internal/config"
)
func TestValidateQuackClientURL(t *testing.T) {
tests := []struct {
name string
url string
token string
allowInsecure bool
wantErr string
}{
{
name: "loopback http allowed",
url: "quack:http://127.0.0.1:9494",
token: "secret",
},
{
name: "native loopback hostport allowed",
url: "quack:127.0.0.1:9494",
token: "secret",
},
{
name: "native loopback slashes allowed",
url: "quack://127.0.0.1:9494",
token: "secret",
},
{
name: "https remote allowed",
url: "quack:https://duck.example.com",
token: "secret",
},
{
name: "explicit insecure remote allowed",
url: "quack:http://duck.example.com",
token: "secret",
allowInsecure: true,
},
{
name: "native remote rejected",
url: "quack:duck.example.com:9494",
token: "secret",
wantErr: "loopback",
},
{
name: "native remote explicitly allowed",
url: "quack:duck.example.com:9494",
token: "secret",
allowInsecure: true,
},
{
name: "token required",
url: "quack:http://127.0.0.1:9494",
wantErr: "token is required",
},
{
name: "plain remote rejected",
url: "quack:http://duck.example.com",
token: "secret",
wantErr: "plain HTTP",
},
{
name: "quack scheme required",
url: "http://127.0.0.1:9494",
token: "secret",
wantErr: "quack",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateQuackClientURL(tt.url, tt.token, tt.allowInsecure)
if tt.wantErr == "" {
require.NoError(t, err)
return
}
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantErr)
})
}
}
func TestLocalDuckDBBackfillTargetScopeUsesCanonicalPath(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "mirror.duckdb")
samePath := filepath.Join(dir, ".", "mirror.duckdb")
otherPath := filepath.Join(dir, "other.duckdb")
got := localDuckDBBackfillTargetScope(path)
assert.Equal(t, got, localDuckDBBackfillTargetScope(samePath))
assert.NotEqual(t, got, localDuckDBBackfillTargetScope(otherPath))
assert.NotContains(t, got, dir)
}
func TestIsStaleQuackConnectionError(t *testing.T) {
assert.True(t, isStaleQuackConnectionError(
errors.New("Invalid Input Error: Invalid connection id"),
))
assert.True(t, isStaleQuackConnectionError(
errors.New("IO Error: Failed to send message: Bad Gateway"),
))
assert.True(t, isStaleQuackConnectionError(
errors.New("Catalog Error: Table Function with name query does not exist!"),
))
assert.False(t, isStaleQuackConnectionError(
errors.New("Catalog Error: Table with name sessions does not exist"),
))
}
func TestRedactQuackURL(t *testing.T) {
got := RedactQuackURL(
"quack:https://account:credential0@duck.example.com/db?token=credential1&password=credential2&api_key=credential3&x=1",
)
assert.NotContains(t, got, "account")
assert.NotContains(t, got, "credential0")
assert.NotContains(t, got, "credential1")
assert.NotContains(t, got, "credential2")
assert.NotContains(t, got, "credential3")
assert.Contains(t, got, "token=%3Credacted%3E")
assert.Contains(t, got, "password=%3Credacted%3E")
assert.Contains(t, got, "api_key=%3Credacted%3E")
assert.Contains(t, got, "x=1")
}
func TestRedactQuackURLNativeTransport(t *testing.T) {
got := RedactQuackURL(
"quack:account:credential0@duck.example.com:9494/db?token=credential1&x=1#credential2",
)
assert.NotContains(t, got, "account")
assert.NotContains(t, got, "credential0")
assert.NotContains(t, got, "credential1")
assert.NotContains(t, got, "credential2")
assert.Contains(t, got, "token=%3Credacted%3E")
assert.Contains(t, got, "x=1")
assert.NotContains(t, got, "#")
}
func TestRedactQuackClientErrorScrubsAttachSecrets(t *testing.T) {
rawURL := "quack:https://account:credential0@duck.example.com/db?token=credential1&x=1"
token := "credential2'quoted"
err := redactQuackClientError(
errors.New(
"Parser Error near "+quackAttachSQL(rawURL, token)+
"; IO Error connecting to https://account:credential0@DUCK.EXAMPLE.COM:443/db?x=1&token=credential1",
),
rawURL,
token,
)
msg := err.Error()
assert.NotContains(t, msg, "account")
assert.NotContains(t, msg, "credential0")
assert.NotContains(t, msg, "credential1")
assert.NotContains(t, msg, "credential2")
assert.NotContains(t, msg, "credential2''quoted")
assert.Contains(t, msg, "<redacted>")
assert.Contains(t, msg, "duck.example.com")
}
func TestRedactQuackClientErrorScrubsNativeDoubleSlashUserinfo(t *testing.T) {
rawURL := "quack://account:credential0@duck.example.com:9494/db?token=credential1&x=1"
err := redactQuackClientError(
errors.New(
"IO Error connecting to account:credential0@duck.example.com:9494/db?x=1&token=credential1",
),
rawURL,
"credential2",
)
msg := err.Error()
assert.NotContains(t, msg, "account")
assert.NotContains(t, msg, "credential0")
assert.NotContains(t, msg, "credential1")
assert.Contains(t, msg, "duck.example.com")
}
func TestRedactQuackClientErrorScrubsNativeRawAtPassword(t *testing.T) {
rawURL := "quack://account:pa@ss@duck.example.com:9494/db?token=credential1&x=1"
err := redactQuackClientError(
errors.New(
"IO Error connecting to account:pa@ss@duck.example.com:9494/db?x=1&token=credential1",
),
rawURL,
"credential2",
)
msg := err.Error()
assert.NotContains(t, msg, "account")
assert.NotContains(t, msg, "pa@ss")
assert.NotContains(t, msg, "ss@duck")
assert.NotContains(t, msg, "credential1")
assert.Contains(t, msg, "duck.example.com")
}
func TestRedactQuackClientErrorScrubsNativeRawSlashPassword(t *testing.T) {
rawURL := "quack://account:pa/ss@duck.example.com:9494/db?token=credential1&x=1"
err := redactQuackClientError(
errors.New(
"IO Error connecting to account:pa/ss@duck.example.com:9494/db?x=1&token=credential1",
),
rawURL,
"credential2",
)
msg := err.Error()
assert.NotContains(t, msg, "account")
assert.NotContains(t, msg, "pa/ss")
assert.NotContains(t, msg, "credential1")
assert.Contains(t, msg, "duck.example.com")
}
func TestRedactQuackClientErrorScrubsNativeRawSlashPasswordDotlessHost(t *testing.T) {
rawURL := "quack://account:pa/ss@myhost/db?token=credential1&x=1"
err := redactQuackClientError(
errors.New(
"IO Error connecting to account:pa/ss@myhost/db?x=1&token=credential1",
),
rawURL,
"credential2",
)
msg := err.Error()
assert.NotContains(t, msg, "account")
assert.NotContains(t, msg, "pa/ss")
assert.NotContains(t, msg, "credential1")
assert.Contains(t, msg, "myhost")
}
func TestRedactQuackClientErrorScrubsNativeSchemeUserinfo(t *testing.T) {
rawURL := "quack:tcp://account:credential0@duck.example.com:9494/db?token=credential1&x=1"
err := redactQuackClientError(
errors.New(
"IO Error connecting to account:credential0@duck.example.com:9494/db?x=1&token=credential1",
),
rawURL,
"credential2",
)
msg := err.Error()
assert.NotContains(t, msg, "account")
assert.NotContains(t, msg, "credential0")
assert.NotContains(t, msg, "credential1")
assert.Contains(t, msg, "duck.example.com")
}
func TestRedactQuackClientErrorPreservesNativeHostWithAtInPath(t *testing.T) {
rawURL := "quack://account:credential0@duck.example.com:9494/db@v2?token=credential1&x=1"
err := redactQuackClientError(
errors.New(
"IO Error connecting to account:credential0@duck.example.com:9494/db@v2?x=1&token=credential1",
),
rawURL,
"credential2",
)
msg := err.Error()
assert.NotContains(t, msg, "account")
assert.NotContains(t, msg, "credential0")
assert.NotContains(t, msg, "credential1")
assert.Contains(t, msg, "duck.example.com")
assert.Contains(t, msg, "db@v2")
}
func TestRedactQuackClientErrorPreservesNativeHostPortWithAtInPath(t *testing.T) {
rawURL := "quack://duck.example.com:9494/db@v2?token=credential1&x=1"
err := redactQuackClientError(
errors.New(
"IO Error connecting to duck.example.com:9494/db@v2?x=1&token=credential1",
),
rawURL,
"credential2",
)
msg := err.Error()
assert.NotContains(t, msg, "credential1")
assert.Contains(t, msg, "duck.example.com")
assert.Contains(t, msg, "9494")
assert.Contains(t, msg, "db@v2")
}
func TestRedactQuackClientErrorScrubsEncodedCredentialValues(t *testing.T) {
rawURL := "quack:https://account:p%40ss@duck.example.com/db?token=s%2Bcret&x=1"
err := redactQuackClientError(
errors.New(
"IO Error connecting to https://account:p%40ss@duck.example.com/db?x=1&token=s%2Bcret",
),
rawURL,
"attach-token",
)
msg := err.Error()
assert.NotContains(t, msg, "account")
assert.NotContains(t, msg, "p%40ss")
assert.NotContains(t, msg, "p@ss")
assert.NotContains(t, msg, "s%2Bcret")
assert.NotContains(t, msg, "s+cret")
assert.Contains(t, msg, "duck.example.com")
}
func TestSyncStateTargetForConfigScopesRemoteURLWithoutSecrets(t *testing.T) {
base := config.DuckDBConfig{
URL: "quack:https://user:secret@duck.example.com/db?token=secret&x=1#frag",
Token: "first-token",
}
sameTargetDifferentSecrets := config.DuckDBConfig{
URL: "quack:https://other:changed@duck.example.com/db?token=changed&x=1#other",
Token: "second-token",
}
got := SyncStateTargetForConfig(base)
require.NotEmpty(t, got)
assert.Equal(t, got, SyncStateTargetForConfig(sameTargetDifferentSecrets))
assert.NotEqual(t, got, SyncStateTargetForConfig(config.DuckDBConfig{
URL: "quack:https://duck-other.example.com/db?x=1",
}))
assert.NotEqual(t, got, SyncStateTargetForConfig(config.DuckDBConfig{
URL: "quack:https://duck.example.com/other-db?x=1",
}))
assert.NotEqual(t, SyncStateTargetForConfig(config.DuckDBConfig{
URL: "quack:https://duck.example.com/db?keyspace=alpha",
}), SyncStateTargetForConfig(config.DuckDBConfig{
URL: "quack:https://duck.example.com/db?keyspace=beta",
}))
assert.NotContains(t, got, "secret")
assert.NotContains(t, got, "first-token")
assert.NotContains(t, got, "second-token")
assert.Empty(t, SyncStateTargetForConfig(config.DuckDBConfig{
Path: "/tmp/agentsview.duckdb",
}))
}
func TestValidateQuackServeURI(t *testing.T) {
tests := []struct {
name string
uri string
allow bool
wantError string
}{
{name: "localhost default port", uri: "quack:localhost"},
{name: "loopback hostport", uri: "quack:127.0.0.1:9494"},
{name: "loopback slashes", uri: "quack://127.0.0.1:9494"},
{name: "loopback ipv6", uri: "quack:[::1]:9494"},
{name: "external denied", uri: "quack:0.0.0.0:9494", wantError: "loopback"},
{name: "external allowed", uri: "quack:0.0.0.0:9494", allow: true},
{name: "scheme required", uri: "http://127.0.0.1:9494", wantError: "quack"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateQuackServeURI(tt.uri, tt.allow)
if tt.wantError == "" {
require.NoError(t, err)
return
}
require.Error(t, err)
assert.Contains(t, err.Error(), tt.wantError)
})
}
}