202 lines
6.1 KiB
Go
202 lines
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.zheromiku.top/Weknora/dobotai-platform/internal/audit"
|
|
)
|
|
|
|
type auditRepoStub struct {
|
|
tenant string
|
|
}
|
|
|
|
func (r *auditRepoStub) ListTurns(_ context.Context, tenant string, _ audit.ListQuery) ([]audit.TurnSummary, error) {
|
|
r.tenant = tenant
|
|
return nil, nil
|
|
}
|
|
func (r *auditRepoStub) GetTurn(context.Context, string, string) (audit.TurnDetail, error) {
|
|
return audit.TurnDetail{}, audit.ErrNotFound
|
|
}
|
|
|
|
func TestRuntimeBuildInfoUsesLinkerVariables(t *testing.T) {
|
|
oldVersion, oldCommitSHA, oldBuildTime := version, commitSHA, buildTime
|
|
t.Cleanup(func() {
|
|
version, commitSHA, buildTime = oldVersion, oldCommitSHA, oldBuildTime
|
|
})
|
|
version = "2.1.0"
|
|
commitSHA = "abc123def456"
|
|
buildTime = "2026-07-27T12:00:00Z"
|
|
got := runtimeBuildInfo()
|
|
if got.Version != version || got.CommitSHA != commitSHA || got.BuildTime != buildTime {
|
|
t.Fatalf("build info=%+v", got)
|
|
}
|
|
}
|
|
|
|
func TestBuildAuditOptionsDisabledIsBackwardCompatible(t *testing.T) {
|
|
opts, err := buildAuditOptions(nil, "", "", "0.0.0.0:8090")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if opts.AuditService != nil || opts.AdminToken != "" {
|
|
t.Fatalf("options=%+v", opts)
|
|
}
|
|
}
|
|
|
|
func TestBuildAuditOptionsRequiresTenantRepositoryAndLoopback(t *testing.T) {
|
|
repo := &auditRepoStub{}
|
|
for _, tc := range []struct {
|
|
name string
|
|
repo audit.TurnRepository
|
|
tenant string
|
|
address string
|
|
}{
|
|
{name: "missing repository", tenant: "tenant", address: "127.0.0.1:8090"},
|
|
{name: "missing tenant", repo: repo, address: "127.0.0.1:8090"},
|
|
{name: "public listener", repo: repo, tenant: "tenant", address: "0.0.0.0:8090"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if _, err := buildAuditOptions(tc.repo, tc.tenant, "admin-secret", tc.address); err == nil {
|
|
t.Fatal("expected configuration error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildAuditOptionsFixesServerSideTenant(t *testing.T) {
|
|
repo := &auditRepoStub{}
|
|
opts, err := buildAuditOptions(repo, " tenant-a ", " admin-secret ", "127.0.0.1:8090")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if opts.AdminToken != "admin-secret" || opts.AuditService == nil {
|
|
t.Fatalf("options=%+v", opts)
|
|
}
|
|
if _, err := opts.AuditService.ListTurns(context.Background(), audit.ListTurnsRequest{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if repo.tenant != "tenant-a" {
|
|
t.Fatalf("tenant=%q", repo.tenant)
|
|
}
|
|
}
|
|
|
|
func TestMainWiresFeedbackIntoDingTalkStream(t *testing.T) {
|
|
data, err := os.ReadFile("main.go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
source := string(data)
|
|
for _, fragment := range []string{
|
|
`"gitea.zheromiku.top/Weknora/dobotai-platform/internal/feedback"`,
|
|
`feedback.NewService(dingCfg.TenantKey, "dingtalk", store)`,
|
|
`dingtalk.NewCardFeedbackHandler(feedbackService)`,
|
|
`dingtalk.NewStreamClient(dingCfg, handler, feedbackHandler)`,
|
|
} {
|
|
if !strings.Contains(source, fragment) {
|
|
t.Errorf("main feedback wiring missing %q", fragment)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMainWiresOptionalDingTalkCardDebugUser(t *testing.T) {
|
|
data, err := os.ReadFile("main.go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
source := string(data)
|
|
for _, fragment := range []string{
|
|
`os.Getenv("DINGTALK_CARD_DEBUG_USER_ID")`,
|
|
`dingtalk.WithCardDebugUserID(cardDebugUserID)`,
|
|
} {
|
|
if !strings.Contains(source, fragment) {
|
|
t.Errorf("main card debug wiring missing %q", fragment)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildOutboundImageRuntimeConfig(t *testing.T) {
|
|
t.Run("disabled", func(t *testing.T) {
|
|
runtime, err := buildOutboundImageRuntime(outboundImageConfig{})
|
|
if err != nil || runtime.rewriter != nil || runtime.handler != nil {
|
|
t.Fatalf("runtime=%+v err=%v", runtime, err)
|
|
}
|
|
})
|
|
t.Run("partial configuration rejected", func(t *testing.T) {
|
|
_, err := buildOutboundImageRuntime(outboundImageConfig{Bucket: "private-bucket"})
|
|
if err == nil {
|
|
t.Fatal("expected configuration error")
|
|
}
|
|
})
|
|
t.Run("complete configuration", func(t *testing.T) {
|
|
runtime, err := buildOutboundImageRuntime(outboundImageConfig{
|
|
WeKnoraBaseURL: "http://127.0.0.1:8080",
|
|
WeKnoraAPIKey: "api-key",
|
|
Endpoint: "https://oss-cn-shenzhen.aliyuncs.com",
|
|
Region: "cn-shenzhen",
|
|
AccessKeyID: "access-key-id",
|
|
AccessKeySecret: "access-key-secret",
|
|
Bucket: "private-bucket",
|
|
PublicBaseURL: "https://weknora-api-test.dobot.cn/dingtalk/i",
|
|
})
|
|
if err != nil || runtime.rewriter == nil || runtime.handler == nil {
|
|
t.Fatalf("runtime=%+v err=%v", runtime, err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestMainWiresStableOutboundImageRuntime(t *testing.T) {
|
|
data, err := os.ReadFile("main.go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
source := string(data)
|
|
for _, fragment := range []string{
|
|
`buildOutboundImageRuntime(outboundImageConfig{`,
|
|
`PublicBaseURL: os.Getenv("DOBOTAI_OUTBOUND_IMAGE_PUBLIC_BASE_URL")`,
|
|
`OutboundRewriter: outboundImages.rewriter`,
|
|
`rootHandler.Handle(outboundImageRoute, outboundImages.handler)`,
|
|
} {
|
|
if !strings.Contains(source, fragment) {
|
|
t.Errorf("main stable image wiring missing %q", fragment)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOutboundOSSConfiguredRequiresStablePublicBaseURL(t *testing.T) {
|
|
for _, key := range []string{
|
|
"DOBOTAI_OUTBOUND_IMAGE_OSS_ENDPOINT",
|
|
"DOBOTAI_OUTBOUND_IMAGE_OSS_REGION",
|
|
"DOBOTAI_OUTBOUND_IMAGE_OSS_ACCESS_KEY_ID",
|
|
"DOBOTAI_OUTBOUND_IMAGE_OSS_ACCESS_KEY_SECRET",
|
|
"DOBOTAI_OUTBOUND_IMAGE_OSS_BUCKET",
|
|
} {
|
|
t.Setenv(key, "configured")
|
|
}
|
|
t.Setenv("DOBOTAI_OUTBOUND_IMAGE_PUBLIC_BASE_URL", "")
|
|
if outboundOSSConfigured() {
|
|
t.Fatal("stable public base URL must be required")
|
|
}
|
|
t.Setenv("DOBOTAI_OUTBOUND_IMAGE_PUBLIC_BASE_URL", "https://img.example/dingtalk/i")
|
|
if !outboundOSSConfigured() {
|
|
t.Fatal("complete stable image configuration should be active")
|
|
}
|
|
}
|
|
|
|
func TestMainDoesNotExposeWebAdministrationOrSetupOnlyMode(t *testing.T) {
|
|
data, err := os.ReadFile("main.go")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
source := string(data)
|
|
for _, forbidden := range []string{"internal/adminweb", "adminweb.", `"/admin/"`, "runSetupOnly"} {
|
|
if strings.Contains(source, forbidden) {
|
|
t.Fatalf("main must not expose web administration: found %q", forbidden)
|
|
}
|
|
}
|
|
if !strings.Contains(source, "DingTalk credentials are required") {
|
|
t.Fatal("main must fail closed when DingTalk credentials are absent")
|
|
}
|
|
}
|