ci / verify (push) Canceled after 0s
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.
123 lines
5.9 KiB
Go
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
|
|
}
|