package saas import ( "context" "crypto/ed25519" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "errors" "os" "testing" "time" "data-terminal-cloud/internal/audit" "data-terminal-cloud/internal/identity" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) func TestOpsFirmwareReleaseLifecycle(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() store := &firmwareIntegrationStore{} service := NewService(pool, audit.NewRepository(), nil, nil) service.ConfigureFirmwareStore(store, 4*time.Minute) var operator 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(&operator.Issuer, &operator.Subject); err != nil { t.Fatal(err) } const tenantID = "10000000-0000-0000-0000-000000000010" keyID := "ops-firmware-" + uuid.NewString() version := "ops-" + uuid.NewString() var releaseID, rolloutID string defer func() { _, _ = pool.Exec(ctx, `DELETE FROM audit_event WHERE request_id LIKE 'ops-firmware-integration-%'`) if rolloutID != "" { _, _ = pool.Exec(ctx, `DELETE FROM firmware_rollout WHERE public_id=$1::uuid`, rolloutID) } if releaseID != "" { _, _ = pool.Exec(ctx, `DELETE FROM device_command WHERE payload->>'release_id'=$1`, releaseID) } if releaseID != "" { _, _ = pool.Exec(ctx, `DELETE FROM firmware_release WHERE public_id=$1::uuid`, releaseID) } _, _ = pool.Exec(ctx, `DELETE FROM firmware_signing_key WHERE key_id=$1`, keyID) }() publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } if err := service.RegisterOpsFirmwareSigningKey(ctx, operator, tenantID, "ops-firmware-integration-key", keyID, base64.StdEncoding.EncodeToString(publicKey)); err != nil { t.Fatal(err) } firmware := []byte("synthetic operations firmware") digestBytes := sha256.Sum256(firmware) digest := hex.EncodeToString(digestBytes[:]) model, hardware := "simulated-terminal", "sim-rev-a" signature := base64.StdEncoding.EncodeToString(ed25519.Sign(privateKey, []byte(firmwareManifest(model, hardware, version, digest, int64(len(firmware)))))) prepared, err := service.PrepareOpsFirmwareRelease(ctx, operator, tenantID, "ops-firmware-integration-prepare", PrepareFirmwareInput{ Model: model, HardwareRevision: hardware, Version: version, ExpectedSize: int64(len(firmware)), ExpectedDigest: digest, Signature: signature, SigningKeyID: keyID, ReleaseChannel: "BETA", Mandatory: true, ReleaseNotes: "维护端集成测试", }) if err != nil || prepared.Status != "UPLOADING" || prepared.HouseholdID != "" || prepared.CreatedSource != "OPS" || prepared.ReleaseChannel != "BETA" || !prepared.Mandatory || prepared.UploadURL == "" { t.Fatalf("prepared=%+v err=%v", prepared, err) } releaseID = prepared.ID ready, err := service.FinalizeOpsFirmwareRelease(ctx, operator, tenantID, releaseID, "ops-firmware-integration-finalize") if err != nil || ready.Status != "READY" || ready.VerifiedAt == nil || ready.PublishedAt == nil || store.verifiedDigest != digest { t.Fatalf("ready=%+v store=%+v err=%v", ready, store, err) } items, err := service.ListOpsFirmwareReleases(ctx, operator, tenantID, model, "READY") if err != nil || len(items) != 1 || items[0].ID != releaseID { t.Fatalf("items=%+v err=%v", items, err) } download, err := service.OpsFirmwareDownload(ctx, operator, tenantID, releaseID, "ops-firmware-integration-download") if err != nil || download.ReleaseID != releaseID || download.SHA256 != digest || download.DownloadURL == "" { t.Fatalf("download=%+v err=%v", download, err) } rollout, err := service.StartOpsFirmwareRollout(ctx, operator, tenantID, releaseID, "ops-firmware-integration-rollout", "维护端OTA", 100, []string{"10000000-0000-0000-0000-000000000511"}) if err != nil || rollout.Targeted != 1 || rollout.HouseholdID != "" || rollout.Status != "RUNNING" { t.Fatalf("rollout=%+v err=%v", rollout, err) } rolloutID = rollout.ID detail, err := service.GetOpsFirmwareRollout(ctx, operator, tenantID, rolloutID) if err != nil || len(detail.Assignments) != 1 || detail.Assignments[0].Status != "ASSIGNED" { t.Fatalf("rollout detail=%+v err=%v", detail, err) } pausedRollout, err := service.PauseOpsFirmwareRollout(ctx, operator, tenantID, rolloutID, "ops-firmware-integration-rollout-pause") if err != nil || pausedRollout.Status != "PAUSED" { t.Fatalf("paused rollout=%+v err=%v", pausedRollout, err) } paused, err := service.PauseOpsFirmwareRelease(ctx, operator, tenantID, releaseID, "ops-firmware-integration-pause") if err != nil || paused.Status != "PAUSED" { t.Fatalf("paused=%+v err=%v", paused, err) } resumed, err := service.ResumeOpsFirmwareRelease(ctx, operator, tenantID, releaseID, "ops-firmware-integration-resume") if err != nil || resumed.Status != "READY" { t.Fatalf("resumed=%+v err=%v", resumed, err) } retired, err := service.RetireOpsFirmwareRelease(ctx, operator, tenantID, releaseID, "ops-firmware-integration-retire") if err != nil || retired.Status != "RETIRED" || len(store.removed) != 0 { t.Fatalf("retired=%+v removed=%v err=%v", retired, store.removed, err) } customer := identity.Principal{Issuer: testIssuer, Subject: testOwnerSubject} if _, err := service.ListOpsFirmwareReleases(ctx, customer, tenantID, "", ""); !errors.Is(err, ErrAccessDenied) { t.Fatalf("customer ops access error=%v", err) } }