Files
story/internal/hardwareprovision/registrar_test.go
T
Codex Local bf1233d5df
ci / verify (push) Canceled after 0s
feat: expand companion platform, device controls, and memoir workflows
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
2026-10-08 10:06:44 +08:00

120 lines
4.3 KiB
Go

package hardwareprovision
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"
"data-terminal-cloud/internal/deviceauth"
)
func TestHTTPRegistrarSignsAndDecryptsDynamicRegistration(t *testing.T) {
const productSecret = "1234567890abcdef-product-secret"
const deviceSecret = "per-device-secret-with-at-least-32-characters"
var received map[string]any
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
if err := json.NewDecoder(request.Body).Decode(&received); err != nil {
t.Fatalf("decode request: %v", err)
}
content := "auth_type=1&device_name=" + received["device_name"].(string) +
"&random_num=" + strconv.FormatInt(int64(received["random_num"].(float64)), 10) +
"&product_key=" + received["product_key"].(string) +
"&timestamp=" + strconv.FormatInt(int64(received["timestamp"].(float64)), 10)
mac := hmac.New(sha256.New, []byte(productSecret))
_, _ = mac.Write([]byte(content))
if received["signature"] != base64.StdEncoding.EncodeToString(mac.Sum(nil)) {
t.Fatal("dynamic registration signature did not match canonical fields")
}
if _, leaked := received["product_secret"]; leaked {
t.Fatal("product secret must not be serialized into the provider request")
}
_ = json.NewEncoder(writer).Encode(map[string]any{"Result": map[string]any{
"payload": encryptDeviceSecretForTest(t, deviceSecret, productSecret), "RTCAppID": "rtc-app",
}})
}))
defer server.Close()
registrar, err := NewHTTPRegistrar(server.URL, productSecret, time.Second)
if err != nil {
t.Fatalf("NewHTTPRegistrar() error = %v", err)
}
registrar.now = func() time.Time { return time.UnixMilli(1_700_000_000_000) }
result, err := registrar.Register(context.Background(), RegisterInput{InstanceID: "instance-1", ProductKey: "product-1", DeviceName: "device-1"})
if err != nil {
t.Fatalf("Register() error = %v", err)
}
if result.DeviceSecret != deviceSecret || result.RTCAppID != "rtc-app" {
t.Fatalf("Register() result = %+v", result)
}
if received["InstanceID"] != "instance-1" || received["product_key"] != "product-1" || received["device_name"] != "device-1" {
t.Fatalf("unexpected registration request: %+v", received)
}
}
func TestDisabledProvisioningDoesNotRequireProductSecret(t *testing.T) {
service, err := NewService(nil, nil, Options{Enabled: false})
if err != nil {
t.Fatalf("NewService() error = %v", err)
}
_, err = service.Provision(context.Background(), deviceauth.Identity{}, "request", ProvisionInput{HardwareID: "device"})
if err != ErrUnavailable {
t.Fatalf("Provision() error = %v, want ErrUnavailable", err)
}
}
func TestHTTPRegistrarRTCAppIDVariantsAndMissingID(t *testing.T) {
for _, field := range []string{"RTCAppID", "rtc_app_id", "missing"} {
t.Run(field, func(t *testing.T) {
const productSecret = "1234567890abcdef-product-secret"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
result := map[string]any{"payload": encryptDeviceSecretForTest(t, "fixture-device-secret-123456789", productSecret)}
if field != "missing" {
result[field] = "rtc-app"
}
_ = json.NewEncoder(w).Encode(map[string]any{"Result": result, "ResponseMetadata": map[string]any{"Error": map[string]any{"CodeN": 0}}})
}))
defer server.Close()
r, err := NewHTTPRegistrar(server.URL, productSecret, time.Second)
if err != nil {
t.Fatal(err)
}
out, err := r.Register(context.Background(), RegisterInput{"instance", "product", "device"})
if field == "missing" {
if err == nil {
t.Fatal("missing RTCAppID was accepted")
}
return
}
if err != nil || out.RTCAppID != "rtc-app" {
t.Fatalf("app ID decoding failed: %v", err)
}
})
}
}
func encryptDeviceSecretForTest(t *testing.T, secret, productSecret string) string {
t.Helper()
key := []byte(productSecret)[:aes.BlockSize]
block, err := aes.NewCipher(key)
if err != nil {
t.Fatal(err)
}
plain := []byte(secret)
padding := aes.BlockSize - len(plain)%aes.BlockSize
for index := 0; index < padding; index++ {
plain = append(plain, byte(padding))
}
encrypted := make([]byte, len(plain))
cipher.NewCBCEncrypter(block, key).CryptBlocks(encrypted, plain)
return base64.StdEncoding.EncodeToString(encrypted)
}