Files
story/internal/hardwareprovision/service_integration_test.go
T
Codex Local 0c2151ace9
ci / verify (push) Canceled after 0s
chore: checkpoint 第一版
2026-09-06 17:04:11 +08:00

109 lines
4.5 KiB
Go

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")
}
}