Files
story/internal/httpapi/device_auth_test.go
T
Codex Local 0c2151ace9
ci / verify (push) Canceled after 0s
chore: checkpoint 第一版
2026-09-06 17:04:11 +08:00

114 lines
4.9 KiB
Go

package httpapi
import (
"errors"
"net/http"
"net/http/httptest"
"testing"
"data-terminal-cloud/internal/deviceauth"
)
type fakeDeviceVerifier struct {
identity deviceauth.Identity
err error
}
func (v fakeDeviceVerifier) Verify(*http.Request) (deviceauth.Identity, error) {
return v.identity, v.err
}
func TestDeviceAuthAddsIdentityContext(t *testing.T) {
expected := deviceauth.Identity{PublicID: "device-1", TenantID: "tenant-1"}
handler := DeviceAuth(fakeDeviceVerifier{identity: expected}, http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
actual, ok := deviceauth.FromContext(request.Context())
if !ok || actual.PublicID != expected.PublicID {
t.Fatalf("identity = %#v, ok = %v", actual, ok)
}
writer.WriteHeader(http.StatusNoContent)
}))
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/device/v2/config", nil))
if recorder.Code != http.StatusNoContent {
t.Fatalf("status = %d", recorder.Code)
}
}
func TestDeviceAuthDoesNotExposeCredentialFailure(t *testing.T) {
for _, authError := range []error{
deviceauth.ErrCredentialsMissing, deviceauth.ErrCredentialInvalid,
deviceauth.ErrDeviceInactive, deviceauth.ErrRequestExpired, deviceauth.ErrReplayDetected,
} {
handler := DeviceAuth(fakeDeviceVerifier{err: authError}, http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
t.Fatal("downstream must not be called")
}))
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/device/v2/config", nil))
if recorder.Code != http.StatusUnauthorized || !containsText(recorder.Body.String(), "DEVICE_AUTHENTICATION_FAILED") {
t.Fatalf("error %v returned status %d and body %s", authError, recorder.Code, recorder.Body.String())
}
}
handler := DeviceAuth(fakeDeviceVerifier{err: errors.New("redis down")}, http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/device/v2/config", nil))
if recorder.Code != http.StatusServiceUnavailable || !containsText(recorder.Body.String(), `"retryable":true`) {
t.Fatalf("unexpected unavailable response: %d %s", recorder.Code, recorder.Body.String())
}
}
func TestRequireDeviceActivation(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) })
handler := RequireDeviceActivation([]string{"ACTIVE"}, next)
activeRequest := httptest.NewRequest(http.MethodPost, "/device/v3/agent-sessions", nil)
activeRequest = activeRequest.WithContext(deviceauth.WithIdentity(activeRequest.Context(), deviceauth.Identity{ActivationState: "ACTIVE"}))
activeRecorder := httptest.NewRecorder()
handler.ServeHTTP(activeRecorder, activeRequest)
if activeRecorder.Code != http.StatusNoContent {
t.Fatalf("active status = %d", activeRecorder.Code)
}
boundRequest := httptest.NewRequest(http.MethodPost, "/device/v3/agent-sessions", nil)
boundRequest = boundRequest.WithContext(deviceauth.WithIdentity(boundRequest.Context(), deviceauth.Identity{ActivationState: "BOUND"}))
boundRecorder := httptest.NewRecorder()
handler.ServeHTTP(boundRecorder, boundRequest)
if boundRecorder.Code != http.StatusConflict || !containsText(boundRecorder.Body.String(), "DEVICE_ACTIVATION_REQUIRED") {
t.Fatalf("bound response = %d %s", boundRecorder.Code, boundRecorder.Body.String())
}
}
func TestRequireHardwareLicense(t *testing.T) {
next := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) })
handler := RequireHardwareLicense(next)
for _, testCase := range []struct {
name string
identity deviceauth.Identity
status int
code string
}{
{name: "hardware license active", identity: deviceauth.Identity{VoiceRuntime: "volc_hardware_agent", HardwareLicenseReady: true}, status: http.StatusNoContent},
{name: "hardware license missing", identity: deviceauth.Identity{VoiceRuntime: "volc_hardware_agent"}, status: http.StatusConflict, code: "HARDWARE_LICENSE_REQUIRED"},
{name: "legacy runtime unaffected", identity: deviceauth.Identity{VoiceRuntime: "legacy_voicechat"}, status: http.StatusNoContent},
} {
t.Run(testCase.name, func(t *testing.T) {
request := httptest.NewRequest(http.MethodPost, "/device/v3/agent-sessions", nil)
request = request.WithContext(deviceauth.WithIdentity(request.Context(), testCase.identity))
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
if recorder.Code != testCase.status || (testCase.code != "" && !containsText(recorder.Body.String(), testCase.code)) {
t.Fatalf("response = %d %s", recorder.Code, recorder.Body.String())
}
})
}
}
func containsText(value, part string) bool {
for index := 0; index+len(part) <= len(value); index++ {
if value[index:index+len(part)] == part {
return true
}
}
return false
}