This commit is contained in:
2026-10-18 22:31:16 +01:00
commit ec38e680d6
64 changed files with 7222 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
// 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)
}
}