package setup_test import ( "bytes" "encoding/json" "log/slog" "net/http" "net/http/httptest" "path/filepath" "testing" "github.com/dariuszjaniak/warp/server/internal/config" "github.com/dariuszjaniak/warp/server/internal/matrix/signing" "github.com/dariuszjaniak/warp/server/internal/setup" ) // newSigningServer is newTestServer with a real signing key, which is what // the roomserver and therefore the syncapi need: a server started without // one has no rooms and so no stream to read. func newSigningServer(t *testing.T, tweak func(*config.Config)) *setup.Server { t.Helper() key, err := signing.Generate("test") if err != nil { t.Fatalf("signing.Generate: %v", err) } keyPath := filepath.Join(t.TempDir(), "signing.key") if err := key.Save(keyPath); err != nil { t.Fatalf("saving the signing key: %v", err) } cfg := config.Default() cfg.Global.ServerName = "warp.example" cfg.Global.PrivateKeyPath = keyPath cfg.Database.ConnectionString = "file:" + t.Name() + "?mode=memory&cache=shared" cfg.Registration.Enabled = true // The limiter keys on the client address, and every httptest request // comes from the same one. cfg.ClientAPI.RateLimit.Enabled = false if tweak != nil { tweak(cfg) } if err := cfg.Validate(); err != nil { t.Fatalf("test config is invalid: %v", err) } srv, err := setup.New(t.Context(), cfg, setup.Options{Logger: slog.New(slog.DiscardHandler)}) if err != nil { t.Fatalf("setup.New: %v", err) } t.Cleanup(func() { _ = srv.Close() }) return srv } // registerThrough walks the registration flow and returns the access token. func registerThrough(t *testing.T, srv *setup.Server, localpart string) string { t.Helper() body := map[string]any{"username": localpart, "password": "sUp3rs3kr1t"} rec := post(t, srv, "/_matrix/client/v3/register", body, "") if rec.Code != http.StatusUnauthorized { t.Fatalf("register status = %d, want 401 (body: %s)", rec.Code, rec.Body.String()) } body["auth"] = map[string]any{ "type": "m.login.dummy", "session": decode[map[string]any](t, rec)["session"], } rec = post(t, srv, "/_matrix/client/v3/register", body, "") if rec.Code != http.StatusOK { t.Fatalf("register status = %d, want 200 (body: %s)", rec.Code, rec.Body.String()) } token, _ := decode[map[string]any](t, rec)["access_token"].(string) if token == "" { t.Fatalf("registration returned no access token: %s", rec.Body.String()) } return token } // put sends a JSON body with PUT through the fully wrapped handler. func put(t *testing.T, srv *setup.Server, path string, body any, token string) *httptest.ResponseRecorder { t.Helper() raw, err := json.Marshal(body) if err != nil { t.Fatal(err) } req := httptest.NewRequest(http.MethodPut, path, bytes.NewReader(raw)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+token) rec := httptest.NewRecorder() srv.Handler().ServeHTTP(rec, req) return rec } // TestSyncIsWiredUp is the Phase 4 counterpart of TestClientAPIIsWiredUp: // it goes through the whole stack rather than a hand-built router, to catch // an endpoint that was written but never registered. func TestSyncIsWiredUp(t *testing.T) { srv := newSigningServer(t, func(c *config.Config) { c.Sync.Presence = true }) token := registerThrough(t, srv, "alice") if srv.Sync() == nil { t.Fatal("a server with a signing key must have a syncapi") } rec := post(t, srv, "/_matrix/client/v3/createRoom", map[string]any{}, token) if rec.Code != http.StatusOK { t.Fatalf("createRoom status = %d (body: %s)", rec.Code, rec.Body.String()) } roomID, _ := decode[map[string]any](t, rec)["room_id"].(string) if roomID == "" { t.Fatalf("createRoom returned no room ID: %s", rec.Body.String()) } for _, path := range []string{ "/_matrix/client/v3/sync", "/_matrix/client/v3/rooms/" + roomID + "/messages?dir=b", "/_matrix/client/v3/presence/@alice:warp.example/status", } { if rec := getWithToken(t, srv, path, token); rec.Code != http.StatusOK { t.Errorf("GET %s = %d, want 200 (body: %s)", path, rec.Code, rec.Body.String()) } } } // TestPresenceIsOffByDefault pins down PLAN.md open question 3: presence is // the noisiest thing in Matrix and a new install does not get it. func TestPresenceIsOffByDefault(t *testing.T) { if config.Default().Sync.Presence { t.Error("presence must be off in a default configuration") } srv := newSigningServer(t, nil) token := registerThrough(t, srv, "alice") // The endpoint is registered and refuses, rather than being absent: a // client that gets a 404 concludes the server is too old to have // presence and stops asking, where a refusal says it is switched off. rec := put(t, srv, "/_matrix/client/v3/presence/@alice:warp.example/status", map[string]any{"presence": "online"}, token) if rec.Code != http.StatusForbidden { t.Errorf("PUT /presence with presence off = %d, want 403 (body: %s)", rec.Code, rec.Body.String()) } if code, _ := decode[map[string]any](t, rec)["errcode"].(string); code != "M_FORBIDDEN" { t.Errorf("errcode = %q, want M_FORBIDDEN", code) } }