- centrifugo.Client.Info probes the info API method; an error body fails - websockets:health ports CentrifugoHealthCheck: exits 1 without an API key or when the probe fails, prints the Setting/Value table otherwise - websockets:generate-vapid-keys prints a new P-256 pair, shows configured keys only truncated, and --update persists them to overrides.yaml - websockets:test-push reads subscriptions from an app-published SubscriptionSource, refuses to send while push is disabled and sends one encrypted push per subscription - no command prints a configured private key or the Centrifugo API key - flare and lighthouse READMEs document the CLI commands
130 lines
4.1 KiB
Go
130 lines
4.1 KiB
Go
package centrifugo
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/summercms/modules/backpack"
|
|
"git.golem15.com/golem15/summercms/modules/bonfire"
|
|
"git.golem15.com/golem15/summercms/modules/compass"
|
|
)
|
|
|
|
const testAPIKey = "test-api-key-7f3c9e"
|
|
|
|
func healthApp(t *testing.T, realtimeYAML string) *backpack.App {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
if err := os.WriteFile(filepath.Join(dir, "realtime.yaml"), []byte(realtimeYAML), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cfg, err := compass.Open(compass.Options{Dir: dir, Env: "development", Environ: []string{}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return backpack.New(cfg)
|
|
}
|
|
|
|
func runHealth(t *testing.T, app *backpack.App) (string, error) {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
root, err := bonfire.NewRootIO("acme", Commands(app), strings.NewReader(""), &buf, &buf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
root.SetArgs([]string{"websockets:health"})
|
|
err = root.Execute()
|
|
return buf.String(), err
|
|
}
|
|
|
|
func TestHealthCommand(t *testing.T) {
|
|
var hits atomic.Int32
|
|
var gotAuth, gotBody atomic.Value
|
|
status := atomic.Int32{}
|
|
answer := atomic.Value{}
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
hits.Add(1)
|
|
if r.URL.Path != "/api/info" || r.Method != http.MethodPost {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
return
|
|
}
|
|
body, _ := io.ReadAll(r.Body)
|
|
gotAuth.Store(r.Header.Get("Authorization"))
|
|
gotBody.Store(string(body))
|
|
w.WriteHeader(int(status.Load()))
|
|
_, _ = io.WriteString(w, answer.Load().(string))
|
|
}))
|
|
defer srv.Close()
|
|
configured := "centrifugo:\n api_url: " + srv.URL + "/api\n api_key: " + testAPIKey + "\n"
|
|
|
|
t.Run("missing API key", func(t *testing.T) {
|
|
out, err := runHealth(t, healthApp(t, "centrifugo:\n api_url: "+srv.URL+"/api\n"))
|
|
if err == nil {
|
|
t.Fatalf("want an error exit:\n%s", out)
|
|
}
|
|
if !strings.Contains(out, "Centrifugo not configured (API key missing)") || !strings.Contains(out, "SUMMER_REALTIME__CENTRIFUGO__API_KEY") {
|
|
t.Fatalf("output:\n%s", out)
|
|
}
|
|
if hits.Load() != 0 {
|
|
t.Fatal("a request was sent without an API key")
|
|
}
|
|
})
|
|
|
|
t.Run("Centrifugo answers info", func(t *testing.T) {
|
|
status.Store(http.StatusOK)
|
|
answer.Store(`{"result":{"nodes":[{"name":"node-1"}]}}`)
|
|
out, err := runHealth(t, healthApp(t, configured))
|
|
if err != nil {
|
|
t.Fatalf("err = %v\n%s", err, out)
|
|
}
|
|
for _, want := range []string{"Checking Centrifugo connection...", "API URL: " + srv.URL + "/api", "Configuration OK", "Setting\tValue", "API URL\t" + srv.URL + "/api", "Enabled\tYes", "API Key Set\tYes"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("output lacks %q:\n%s", want, out)
|
|
}
|
|
}
|
|
if gotAuth.Load() != "apikey "+testAPIKey || gotBody.Load() != "{}" {
|
|
t.Fatalf("info request: auth %v body %v", gotAuth.Load(), gotBody.Load())
|
|
}
|
|
if strings.Contains(out, testAPIKey) {
|
|
t.Fatalf("output contains the API key:\n%s", out)
|
|
}
|
|
})
|
|
|
|
t.Run("Centrifugo answers 500", func(t *testing.T) {
|
|
status.Store(http.StatusInternalServerError)
|
|
answer.Store(`oops`)
|
|
out, err := runHealth(t, healthApp(t, configured))
|
|
if err == nil || !strings.Contains(out, "Connection check failed:") || strings.Contains(out, "Configuration OK") {
|
|
t.Fatalf("err = %v\n%s", err, out)
|
|
}
|
|
if strings.Contains(out, testAPIKey) {
|
|
t.Fatalf("output contains the API key:\n%s", out)
|
|
}
|
|
})
|
|
|
|
t.Run("Centrifugo answers an error body", func(t *testing.T) {
|
|
status.Store(http.StatusOK)
|
|
answer.Store(`{"error":{"code":101,"message":"unauthorized"}}`)
|
|
out, err := runHealth(t, healthApp(t, configured))
|
|
if err == nil || !strings.Contains(out, "Connection check failed:") || !strings.Contains(out, "unauthorized") {
|
|
t.Fatalf("err = %v\n%s", err, out)
|
|
}
|
|
})
|
|
|
|
t.Run("Centrifugo unreachable", func(t *testing.T) {
|
|
dead := httptest.NewServer(http.NotFoundHandler())
|
|
url := dead.URL
|
|
dead.Close()
|
|
out, err := runHealth(t, healthApp(t, "centrifugo:\n api_url: "+url+"/api\n api_key: "+testAPIKey+"\n"))
|
|
if err == nil || !strings.Contains(out, "Connection check failed:") || strings.Contains(out, testAPIKey) {
|
|
t.Fatalf("err = %v\n%s", err, out)
|
|
}
|
|
})
|
|
}
|