106 lines
3.5 KiB
Go
106 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestOperationalStatusProviderBuildsLiveComponents(t *testing.T) {
|
|
health := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer health.Close()
|
|
|
|
checked := time.Date(2026, 8, 13, 10, 0, 0, 0, time.UTC)
|
|
provider := &operationalStatusProvider{
|
|
metrics: operationalMetricsStub{value: operationalMetrics{
|
|
QuestionPending: 2, FAQFailed: 1, SupportPending: 3,
|
|
LastQuestionSuccess: checked.Add(-time.Minute),
|
|
}},
|
|
weknoraHealthURL: health.URL,
|
|
httpClient: health.Client(),
|
|
now: func() time.Time { return checked },
|
|
version: "settings-test",
|
|
streamRunning: true,
|
|
questionRunning: true,
|
|
faqRunning: false,
|
|
supportRunning: false,
|
|
questionBound: true,
|
|
faqBound: false,
|
|
supportBound: true,
|
|
ossConfigured: true,
|
|
}
|
|
components, err := provider.Components(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
byName := componentMap(components)
|
|
assertComponent(t, byName, "运行版本", "ok", "version_ok")
|
|
assertComponent(t, byName, "PostgreSQL", "ok", "database_ok")
|
|
assertComponent(t, byName, "钉钉Stream", "ok", "stream_running")
|
|
assertComponent(t, byName, "WeKnora", "ok", "health_ok")
|
|
assertComponent(t, byName, "问题表Outbox", "warning", "outbox_pending")
|
|
assertComponent(t, byName, "FAQ Outbox", "error", "outbox_failed")
|
|
assertComponent(t, byName, "技术支持通知", "blocked", "faq_binding_required")
|
|
assertComponent(t, byName, "问题表Dispatcher", "ok", "dispatcher_running")
|
|
assertComponent(t, byName, "FAQ Dispatcher", "disabled", "binding_missing")
|
|
assertComponent(t, byName, "Private OSS图片", "ok", "oss_configured")
|
|
for _, component := range components {
|
|
for _, forbidden := range []string{health.URL, "cid-secret-group", "base-secret", "access-token"} {
|
|
if strings.Contains(component.Detail, forbidden) {
|
|
t.Fatalf("component %q leaks sensitive identifier %q", component.Name, forbidden)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOperationalStatusProviderIsolatesWeKnoraFailure(t *testing.T) {
|
|
provider := &operationalStatusProvider{
|
|
metrics: operationalMetricsStub{value: operationalMetrics{}},
|
|
weknoraHealthURL: "http://127.0.0.1:1/health",
|
|
httpClient: &http.Client{Timeout: 50 * time.Millisecond},
|
|
now: time.Now,
|
|
}
|
|
components, err := provider.Components(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
byName := componentMap(components)
|
|
assertComponent(t, byName, "PostgreSQL", "ok", "database_ok")
|
|
assertComponent(t, byName, "WeKnora", "error", "health_probe_failed")
|
|
}
|
|
|
|
type operationalMetricsStub struct {
|
|
value operationalMetrics
|
|
err error
|
|
}
|
|
|
|
func (s operationalMetricsStub) ReadOperationalMetrics(context.Context, string) (operationalMetrics, error) {
|
|
return s.value, s.err
|
|
}
|
|
|
|
func (s operationalMetricsStub) Ping(context.Context) error { return s.err }
|
|
|
|
func componentMap(values []adminComponent) map[string]adminComponent {
|
|
out := make(map[string]adminComponent, len(values))
|
|
for _, value := range values {
|
|
out[value.Name] = value
|
|
}
|
|
return out
|
|
}
|
|
|
|
func assertComponent(t *testing.T, values map[string]adminComponent, name, state, code string) {
|
|
t.Helper()
|
|
value, ok := values[name]
|
|
if !ok {
|
|
t.Fatalf("missing component %q: %+v", name, values)
|
|
}
|
|
if value.State != state || value.Code != code {
|
|
t.Fatalf("component %q=%+v want state=%q code=%q", name, value, state, code)
|
|
}
|
|
}
|