110 lines
3.9 KiB
Go
110 lines
3.9 KiB
Go
// Copyright (C) 2026 Dcntrlized
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published
|
|
// by the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestRoutesConstruct builds exactly the wiring main() builds and fails on a
|
|
// panic. http.ServeMux panics at registration time when the same pattern is
|
|
// registered twice, which kills the process before it binds :8080 — and
|
|
// go build/vet/gofmt/go test cannot see it unless a test constructs the mux.
|
|
// This test exists because a duplicate /auth/logout registration shipped and
|
|
// only a real launch caught it.
|
|
func TestRoutesConstruct(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Fatalf("route registration panicked — the service would not boot: %v", r)
|
|
}
|
|
}()
|
|
|
|
apiMux := newAPIMux()
|
|
if apiMux == nil {
|
|
t.Fatal("newAPIMux returned nil")
|
|
}
|
|
outer := newOuterMux(apiMux)
|
|
if outer == nil {
|
|
t.Fatal("newOuterMux returned nil")
|
|
}
|
|
}
|
|
|
|
// TestWebSocketPatternIsFree proves main.go leaves /ws/alarm to the realtime
|
|
// layer: registering it on the outer mux must not conflict with anything
|
|
// main.go registered (realtime.Register does exactly this at boot).
|
|
func TestWebSocketPatternIsFree(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Fatalf("/ws/alarm conflicts with a pattern registered by main.go: %v", r)
|
|
}
|
|
}()
|
|
|
|
outer := newOuterMux(newAPIMux())
|
|
outer.Handle("/ws/alarm", http.NotFoundHandler())
|
|
}
|
|
|
|
// TestManifestThroughOuterChain serves GET /_manifest through the real outer
|
|
// mux (logging + CORS chain included), which is what a caller hits.
|
|
func TestManifestThroughOuterChain(t *testing.T) {
|
|
outer := newOuterMux(newAPIMux())
|
|
|
|
rec := httptest.NewRecorder()
|
|
outer.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/_manifest", nil))
|
|
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("GET /_manifest = %d, want 200 (body: %s)", rec.Code, rec.Body.String())
|
|
}
|
|
if ct := rec.Header().Get("Content-Type"); !strings.Contains(ct, "application/json") {
|
|
t.Fatalf("GET /_manifest Content-Type = %q, want application/json", ct)
|
|
}
|
|
if body := rec.Body.String(); !strings.Contains(body, `"name": "Alarm"`) && !strings.Contains(body, `"name":"Alarm"`) {
|
|
t.Fatalf("GET /_manifest body does not name the service: %s", body)
|
|
}
|
|
}
|
|
|
|
// TestLogoutServesBothMethods pins the method-aware registration: GET (bodyless
|
|
// no-op) and POST (revoke the refresh token) must both be routed, and any other
|
|
// verb must be rejected with 405. The handler proxies to dcntrlized-auth, so the
|
|
// requests carry a short deadline: the assertion is about routing, and the test
|
|
// must not depend on (or wait for) a live auth service.
|
|
func TestLogoutServesBothMethods(t *testing.T) {
|
|
outer := newOuterMux(newAPIMux())
|
|
|
|
do := func(method string) int {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
|
|
defer cancel()
|
|
req := httptest.NewRequest(method, "/auth/logout", nil).WithContext(ctx)
|
|
rec := httptest.NewRecorder()
|
|
outer.ServeHTTP(rec, req)
|
|
return rec.Code
|
|
}
|
|
|
|
for _, method := range []string{http.MethodGet, http.MethodPost} {
|
|
if code := do(method); code == http.StatusMethodNotAllowed || code == http.StatusNotFound {
|
|
t.Fatalf("%s /auth/logout = %d, want the method to be routed", method, code)
|
|
}
|
|
}
|
|
|
|
if code := do(http.MethodPut); code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("PUT /auth/logout = %d, want 405", code)
|
|
}
|
|
}
|