✨ feat(server): api.v2_enabled hot-reload via middleware gate (ADR-0023 Phase 4) (#56)
Co-authored-by: Gabriel Radureau <arcodange@gmail.com> Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
This commit was merged in pull request #56.
This commit is contained in:
84
pkg/server/v2_gate_test.go
Normal file
84
pkg/server/v2_gate_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"dance-lessons-coach/pkg/config"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestV2EnabledGate_BlocksWhenDisabled verifies the ADR-0023 Phase 4
|
||||
// hot-reload security property: when api.v2_enabled is false, ANY request
|
||||
// to /api/v2/* returns 404 with a JSON body, not a 200, not a panic.
|
||||
func TestV2EnabledGate_BlocksWhenDisabled(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
cfg.API.V2Enabled = false // explicit, even though it is the zero value
|
||||
s := NewServer(cfg, context.Background())
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v2/greet", strings.NewReader(`{"name":"world"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusNotFound, w.Code, "v2 disabled should 404")
|
||||
assert.Contains(t, w.Body.String(), "v2 API is currently disabled",
|
||||
"response should explain why")
|
||||
assert.Equal(t, "application/json", w.Header().Get("Content-Type"))
|
||||
}
|
||||
|
||||
// TestV2EnabledGate_PassesWhenEnabled verifies the gate lets requests
|
||||
// through to the actual v2 handler when api.v2_enabled is true. We use
|
||||
// a v2 endpoint that exists and responds with a 2xx so we can assert
|
||||
// "got past the gate, hit the handler".
|
||||
func TestV2EnabledGate_PassesWhenEnabled(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
cfg.API.V2Enabled = true
|
||||
s := NewServer(cfg, context.Background())
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v2/greet", strings.NewReader(`{"name":"world"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.router.ServeHTTP(w, req)
|
||||
|
||||
// 200 = v2 handler executed. Anything other than 404 with the gate's
|
||||
// message proves the gate let the request through.
|
||||
assert.NotEqual(t, http.StatusNotFound, w.Code, "v2 enabled should not return 404 from gate")
|
||||
assert.NotContains(t, w.Body.String(), "v2 API is currently disabled",
|
||||
"gate message must NOT appear when enabled")
|
||||
}
|
||||
|
||||
// TestV2EnabledGate_HotReloadEffect simulates the ADR-0023 Phase 4
|
||||
// scenario: the same Server (same router) sees opposite responses
|
||||
// before and after a config flip — proving the gate reads the live
|
||||
// config rather than a snapshot from setupRoutes.
|
||||
func TestV2EnabledGate_HotReloadEffect(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
cfg.API.V2Enabled = false
|
||||
s := NewServer(cfg, context.Background())
|
||||
|
||||
// Round 1: disabled
|
||||
req1 := httptest.NewRequest(http.MethodPost, "/api/v2/greet", strings.NewReader(`{"name":"a"}`))
|
||||
req1.Header.Set("Content-Type", "application/json")
|
||||
w1 := httptest.NewRecorder()
|
||||
s.router.ServeHTTP(w1, req1)
|
||||
assert.Equal(t, http.StatusNotFound, w1.Code, "round 1 (disabled) should 404")
|
||||
|
||||
// Flip the config. In production, Config.WatchAndApply does this on
|
||||
// file change; here we set the field directly to simulate the result.
|
||||
cfg.API.V2Enabled = true
|
||||
|
||||
// Round 2: enabled — same Server, same router, just the config flipped
|
||||
req2 := httptest.NewRequest(http.MethodPost, "/api/v2/greet", strings.NewReader(`{"name":"b"}`))
|
||||
req2.Header.Set("Content-Type", "application/json")
|
||||
w2 := httptest.NewRecorder()
|
||||
s.router.ServeHTTP(w2, req2)
|
||||
assert.NotEqual(t, http.StatusNotFound, w2.Code, "round 2 (enabled) should NOT 404")
|
||||
assert.NotContains(t, w2.Body.String(), "v2 API is currently disabled")
|
||||
}
|
||||
Reference in New Issue
Block a user