package devicereset import ( "context" "crypto/sha256" "encoding/hex" "encoding/json" "errors" "os" "testing" "time" "data-terminal-cloud/internal/audit" "data-terminal-cloud/internal/deviceauth" "data-terminal-cloud/internal/idempotency" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) func TestFactoryResetUnbindsAndSuspendsProviderCredential(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() serial := sha256.Sum256([]byte(devicePublicID)) const tenantID = "10000000-0000-0000-0000-000000000001" if _, 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,'factory-reset-test','registered',$5,'ACTIVE','integration-test')`, deviceID, devicePublicID, tenantID, hex.EncodeToString(serial[:]), "test://"+devicePublicID); err != nil { t.Fatal(err) } if _, err := pool.Exec(ctx, `INSERT INTO device_twin(tenant_id,device_id,desired_revision,desired_config) VALUES($1::uuid,$2::uuid,2,'{"voice_runtime":"volc_hardware_agent"}'::jsonb)`, tenantID, deviceID); err != nil { t.Fatal(err) } if _, err := pool.Exec(ctx, ` INSERT INTO device_binding(tenant_id,device_id,household_id,subject_id,reason) SELECT $1::uuid,$2::uuid,h.id,sp.id,'factory-reset-integration' FROM household h JOIN subject_profile sp ON sp.household_id=h.id AND sp.tenant_id=h.tenant_id WHERE h.public_id='10000000-0000-0000-0000-000000000211'::uuid AND sp.public_id='10000000-0000-0000-0000-000000000411'::uuid`, tenantID, deviceID); err != nil { t.Fatal(err) } if _, err := pool.Exec(ctx, ` INSERT INTO device_presence_state(tenant_id,device_id,household_id,subject_id,online,last_seen_at,changed_at) SELECT $1::uuid,$2::uuid,h.id,sp.id,true,now(),now() FROM household h JOIN subject_profile sp ON sp.household_id=h.id AND sp.tenant_id=h.tenant_id WHERE h.public_id='10000000-0000-0000-0000-000000000211'::uuid AND sp.public_id='10000000-0000-0000-0000-000000000411'::uuid`, tenantID, deviceID); err != nil { t.Fatal(err) } if _, err := pool.Exec(ctx, ` INSERT INTO hardware_agent_device_binding(tenant_id,device_id,product_key,provider_device_ref,credential_ref,bot_id,transport,status) VALUES($1::uuid,$2::uuid,'product-test','device-test',$3,'bot-test','low_load','ACTIVE')`, tenantID, deviceID, "volc-device-secret://"+devicePublicID); err != nil { t.Fatal(err) } if _, err := pool.Exec(ctx, ` INSERT INTO hardware_agent_provider_credential(tenant_id,device_id,credential_ref,instance_id,product_key,provider_device_name,hardware_id,encrypted_secret,nonce,status) VALUES($1::uuid,$2::uuid,$3,'instance-test','product-test','device-test','hardware-test',$4,$5,'ACTIVE')`, tenantID, deviceID, "volc-device-secret://"+devicePublicID, []byte("encrypted"), []byte("nonce")); err != nil { t.Fatal(err) } t.Cleanup(func() { _, _ = pool.Exec(ctx, `DELETE FROM idempotency_record WHERE actor_key=$1`, "device:"+devicePublicID) _, _ = pool.Exec(ctx, `DELETE FROM audit_event WHERE resource_id=$1::uuid`, devicePublicID) _, _ = pool.Exec(ctx, `DELETE FROM web_event WHERE resource_public_id=$1::uuid`, devicePublicID) _, _ = pool.Exec(ctx, `DELETE FROM device_presence_state WHERE device_id=$1::uuid`, deviceID) _, _ = pool.Exec(ctx, `DELETE FROM device_claim WHERE device_id=$1::uuid`, deviceID) _, _ = 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_binding 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_twin WHERE device_id=$1::uuid`, deviceID) _, _ = pool.Exec(ctx, `DELETE FROM device WHERE id=$1::uuid`, deviceID) }) service, err := NewService(pool, idempotency.NewExecutor(pool), audit.NewRepository()) if err != nil { t.Fatal(err) } input := Input{ ResetID: uuid.NewString(), ResetCounter: 1, OccurredAt: time.Now().UTC(), Trigger: "physical_long_press", FirmwareVersion: "1.1.0", PendingTurnCount: 0, } raw, _ := json.Marshal(input) identity := deviceauth.Identity{ID: deviceID, PublicID: devicePublicID, TenantID: tenantID, Status: "registered", ActivationState: "ACTIVE"} first, replayed, err := service.Reset(ctx, identity, "factory-reset-integration-key", "factory-reset-integration-request", raw, input) if err != nil || replayed { t.Fatalf("first reset response=%+v replayed=%v err=%v", first, replayed, err) } second, replayed, err := service.Reset(ctx, identity, "factory-reset-integration-key", "factory-reset-integration-request-replay", raw, input) if err != nil || !replayed || second.Claim.Code != first.Claim.Code { t.Fatalf("replay response=%+v replayed=%v err=%v", second, replayed, err) } var state, providerStatus, bindingStatus string var resetCounter int64 var activeBindings, bindingResetEvents int var online bool if err := pool.QueryRow(ctx, `SELECT activation_state,factory_reset_counter FROM device WHERE id=$1::uuid`, deviceID).Scan(&state, &resetCounter); err != nil { t.Fatal(err) } if err := pool.QueryRow(ctx, `SELECT count(*) FROM device_binding WHERE device_id=$1::uuid AND unbound_at IS NULL`, deviceID).Scan(&activeBindings); err != nil { t.Fatal(err) } if err := pool.QueryRow(ctx, `SELECT status FROM hardware_agent_provider_credential WHERE device_id=$1::uuid`, deviceID).Scan(&providerStatus); err != nil { t.Fatal(err) } if err := pool.QueryRow(ctx, `SELECT status FROM hardware_agent_device_binding WHERE device_id=$1::uuid`, deviceID).Scan(&bindingStatus); err != nil { t.Fatal(err) } if err := pool.QueryRow(ctx, `SELECT online FROM device_presence_state WHERE device_id=$1::uuid`, deviceID).Scan(&online); err != nil { t.Fatal(err) } if err := pool.QueryRow(ctx, `SELECT count(*) FROM web_event WHERE resource_public_id=$1::uuid AND event_type='device.binding.reset'`, devicePublicID).Scan(&bindingResetEvents); err != nil { t.Fatal(err) } if state != "CLAIMABLE" || resetCounter != 1 || activeBindings != 0 || providerStatus != "SUSPENDED" || bindingStatus != "DISABLED" || online || bindingResetEvents != 1 || !first.BindingRemoved || first.LicenseAction != "RETAINED" || first.Claim.Code == "" { t.Fatalf("reset state=%s counter=%d bindings=%d provider=%s binding=%s online=%v reset_events=%d response=%+v", state, resetCounter, activeBindings, providerStatus, bindingStatus, online, bindingResetEvents, first) } } func TestFactoryResetRejectsPendingTranscriptsBeforeDatabaseWork(t *testing.T) { service := &Service{} input := Input{ResetID: uuid.NewString(), ResetCounter: 1, OccurredAt: time.Now().UTC(), Trigger: "device_menu", FirmwareVersion: "1.0.0", PendingTurnCount: 1} _, _, err := service.Reset(context.Background(), deviceauth.Identity{}, "factory-reset-pending-turns", "request", []byte(`{}`), input) if !errors.Is(err, ErrPendingTranscripts) { t.Fatalf("error=%v", err) } }