package hardwareprovision import ( "context" "os" "strings" "sync" "testing" "time" "data-terminal-cloud/internal/audit" "data-terminal-cloud/internal/deviceauth" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) type countingRegistrar struct { mu sync.Mutex calls int } func (r *countingRegistrar) Register(_ context.Context, _ RegisterInput) (RegisterResult, error) { r.mu.Lock() defer r.mu.Unlock() r.calls++ return RegisterResult{DeviceSecret: "provider-device-secret-with-at-least-32-characters"}, nil } func TestProvisionPersistsEncryptedCredentialAndReusesRegistration(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) } t.Cleanup(pool.Close) deviceID := uuid.NewString() devicePublicID := uuid.NewString() const tenantID = "10000000-0000-0000-0000-000000000001" _, err = pool.Exec(ctx, ` INSERT INTO device(id,public_id,tenant_id,serial_digest,model,status,credential_ref,activation_state,activation_reason) VALUES($1::uuid,$2::uuid,$3::uuid,$4,'hardware-provision-test','registered',$5,'BOUND','integration-test')`, deviceID, devicePublicID, tenantID, strings.Repeat("9", 64), "test://"+devicePublicID) if err != nil { t.Fatal(err) } _, err = pool.Exec(ctx, ` INSERT INTO device_twin(tenant_id,device_id,desired_revision,desired_config) VALUES($1::uuid,$2::uuid,1,'{"voice_runtime":"volc_hardware_agent"}'::jsonb)`, tenantID, deviceID) if err != nil { t.Fatal(err) } t.Cleanup(func() { _, _ = pool.Exec(ctx, `DELETE FROM audit_event WHERE resource_id=$1::uuid`, devicePublicID) _, _ = pool.Exec(ctx, `DELETE FROM hardware_agent_provider_credential WHERE device_id=$1::uuid`, deviceID) _, _ = pool.Exec(ctx, `DELETE FROM hardware_agent_device_binding WHERE device_id=$1::uuid`, deviceID) _, _ = pool.Exec(ctx, `DELETE FROM device_twin WHERE device_id=$1::uuid`, deviceID) _, _ = pool.Exec(ctx, `DELETE FROM device_activation_transition WHERE device_id=$1::uuid`, deviceID) _, _ = pool.Exec(ctx, `DELETE FROM device WHERE id=$1::uuid`, deviceID) }) registrar := &countingRegistrar{} service, err := NewService(pool, audit.NewRepository(), Options{ Enabled: true, InstanceID: "instance-1", ProductKey: "product-1", BotID: "bot-test", Transport: "low_load", MinimumSDKVersion: "1.1.0", ProductSecret: "unused-with-mock-registrar", CredentialMasterKey: strings.Repeat("ab", 32), RegisterTimeout: time.Second, DeviceNamePrefix: "dt-", Registrar: registrar, }) if err != nil { t.Fatal(err) } identity := deviceauth.Identity{ID: deviceID, PublicID: devicePublicID, TenantID: tenantID, Status: "registered"} first, err := service.Provision(ctx, identity, "request-1", ProvisionInput{HardwareID: "hardware-1"}) if err != nil { t.Fatal(err) } second, err := service.Provision(ctx, identity, "request-2", ProvisionInput{HardwareID: "hardware-1"}) if err != nil { t.Fatal(err) } if first.DeviceSecret != second.DeviceSecret || first.CredentialSource != "platform_dynamic_registration" || second.CredentialSource != "platform_stored" { t.Fatalf("unexpected provisioning responses: first=%+v second=%+v", first, second) } registrar.mu.Lock() calls := registrar.calls registrar.mu.Unlock() if calls != 1 { t.Fatalf("registrar calls = %d, want 1", calls) } var activationState string if err := pool.QueryRow(ctx, `SELECT activation_state FROM device WHERE id=$1::uuid`, deviceID).Scan(&activationState); err != nil || activationState != "ACTIVE" { t.Fatalf("activation state=%s err=%v, want ACTIVE", activationState, err) } var encrypted []byte var productKey, deviceName, credentialRef, botID, transport, minimumSDK string if err := pool.QueryRow(ctx, ` SELECT pc.encrypted_secret,hb.product_key,hb.provider_device_ref,hb.credential_ref,hb.bot_id,hb.transport,hb.minimum_sdk_version FROM hardware_agent_provider_credential pc JOIN hardware_agent_device_binding hb ON hb.device_id=pc.device_id WHERE pc.device_id=$1::uuid`, deviceID).Scan(&encrypted, &productKey, &deviceName, &credentialRef, &botID, &transport, &minimumSDK); err != nil { t.Fatal(err) } if strings.Contains(string(encrypted), first.DeviceSecret) || productKey != "product-1" || deviceName != first.DeviceName || credentialRef != first.CredentialRef || botID != "bot-test" || transport != "low_load" || minimumSDK != "1.1.0" { t.Fatalf("credential was not stored/bound safely") } }