Files
story/internal/opsdevice/service_integration_test.go
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

123 lines
5.9 KiB
Go

package opsdevice
import (
"context"
"encoding/hex"
"errors"
"os"
"regexp"
"strings"
"testing"
"data-terminal-cloud/internal/audit"
"data-terminal-cloud/internal/deviceauth"
"data-terminal-cloud/internal/devicebinding"
"data-terminal-cloud/internal/identity"
"github.com/jackc/pgx/v5/pgxpool"
)
func TestOperationsDeviceCreatesPhysicalConfirmIdentity(t *testing.T) {
databaseURL := os.Getenv("TEST_DATABASE_URL")
if databaseURL == "" {
t.Skip("TEST_DATABASE_URL is not set")
}
ctx := context.Background()
pool, err := pgxpool.New(ctx, databaseURL)
if err != nil {
t.Fatal(err)
}
defer pool.Close()
resolver, err := deviceauth.NewDatabaseSecretResolver(pool, "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f")
if err != nil {
t.Fatal(err)
}
service, err := NewService(pool, resolver, audit.NewRepository(), Options{
OnboardingMasterKey: strings.Repeat("15", 32),
DeviceOrigin: "https://platform.example/onboarding/entry",
})
if err != nil {
t.Fatal(err)
}
principal := integrationOperatorPrincipal(t, ctx, pool)
const tenantID = "10000000-0000-0000-0000-000000000010"
created, err := service.Create(ctx, principal, tenantID, "ops-device-integration-create", CreateInput{Model: "ECHOEAR-S3"})
if err != nil {
t.Fatal(err)
}
deviceID := created.Device.ID
defer func() {
_, _ = pool.Exec(ctx, `DELETE FROM audit_event WHERE resource_id=$1::uuid`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device_activation_transition WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device_onboarding_key WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device_twin WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID)
_, _ = pool.Exec(ctx, `DELETE FROM device WHERE public_id=$1::uuid`, deviceID)
}()
credential := created.Credential
if !regexp.MustCompile(`^KP01-[0-9A-F]{12}$`).MatchString(credential.FactoryID) || credential.FactoryID != created.Device.FactoryID || credential.Model != "ECHOEAR-S3" || credential.Origin != "https://platform.example" || credential.KeyVersion != 1 {
t.Fatalf("unexpected burn-in identity: %+v", credential)
}
key, err := hex.DecodeString(credential.DeviceKeyHex)
if err != nil || len(key) != 32 {
t.Fatalf("invalid device key: len=%d err=%v", len(key), err)
}
if created.Device.EnrollmentMode != "physical_confirm" || created.Device.ActivationState != "CLAIMABLE" || created.Device.Bound || created.Device.CredentialStatus != "active" || created.Device.KeyVersion != 1 {
t.Fatalf("unexpected created device: %+v", created.Device)
}
var released bool
var desiredConfig string
var activeKeys, legacyCredentials, claims, bindings int
if err := pool.QueryRow(ctx, `SELECT onboarding_released_at IS NOT NULL FROM device WHERE public_id=$1::uuid`, deviceID).Scan(&released); err != nil {
t.Fatal(err)
}
if err := pool.QueryRow(ctx, `SELECT desired_config::text FROM device_twin WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID).Scan(&desiredConfig); err != nil {
t.Fatal(err)
}
if err := pool.QueryRow(ctx, `SELECT
(SELECT count(*) FROM device_onboarding_key WHERE device_id=d.id AND status='ACTIVE'),
(SELECT count(*) FROM device_credential WHERE device_id=d.id),
(SELECT count(*) FROM device_claim WHERE device_id=d.id),
(SELECT count(*) FROM device_binding WHERE device_id=d.id AND unbound_at IS NULL)
FROM device d WHERE d.public_id=$1::uuid`, deviceID).Scan(&activeKeys, &legacyCredentials, &claims, &bindings); err != nil {
t.Fatal(err)
}
if !released || desiredConfig != "{}" || activeKeys != 1 || legacyCredentials != 0 || claims != 0 || bindings != 0 {
t.Fatalf("device was not created as direct BLE inventory: released=%v desired=%s active_keys=%d legacy_credentials=%d claims=%d bindings=%d", released, desiredConfig, activeKeys, legacyCredentials, claims, bindings)
}
if _, err := service.IssueDeviceClaim(ctx, principal, tenantID, deviceID, "ops-device-integration-legacy-claim"); !errors.Is(err, devicebinding.ErrConfirmationRequired) {
t.Fatalf("physical-confirm device accepted a legacy claim: %v", err)
}
rotated, err := service.RotateCredential(ctx, principal, tenantID, deviceID, "ops-device-integration-rotate")
if err != nil || rotated.KeyVersion != 2 || rotated.DeviceKeyHex == credential.DeviceKeyHex || rotated.FactoryID != credential.FactoryID {
t.Fatalf("unexpected credential rotation: %+v err=%v", rotated, err)
}
var activeVersion, revokedKeys int
if err := pool.QueryRow(ctx, `SELECT max(key_version) FILTER (WHERE status='ACTIVE'),count(*) FILTER (WHERE status='REVOKED') FROM device_onboarding_key WHERE device_id=(SELECT id FROM device WHERE public_id=$1::uuid)`, deviceID).Scan(&activeVersion, &revokedKeys); err != nil {
t.Fatal(err)
}
if activeVersion != 2 || revokedKeys != 1 {
t.Fatalf("unexpected rotated key state: active=%d revoked=%d", activeVersion, revokedKeys)
}
var version int64
if err := pool.QueryRow(ctx, `SELECT version FROM device WHERE public_id=$1::uuid`, deviceID).Scan(&version); err != nil {
t.Fatal(err)
}
retired, err := service.Delete(ctx, principal, tenantID, deviceID, "ops-device-integration-delete", version)
if err != nil || retired.Status != "revoked" || retired.ActivationState != "REVOKED" || retired.CredentialStatus != "missing" {
t.Fatalf("unexpected delete result: %+v err=%v", retired, err)
}
}
func integrationOperatorPrincipal(t *testing.T, ctx context.Context, pool *pgxpool.Pool) identity.Principal {
t.Helper()
var principal identity.Principal
if err := pool.QueryRow(ctx, `SELECT oidc_issuer,oidc_subject FROM user_account WHERE public_id='10000000-0000-0000-0000-000000000114'::uuid AND status='active' AND account_kind='operator'`).Scan(&principal.Issuer, &principal.Subject); err != nil {
t.Fatalf("resolve integration operator: %v", err)
}
return principal
}