package saas import ( "context" "crypto/ed25519" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "errors" "os" "strings" "testing" "time" "data-terminal-cloud/internal/audit" "data-terminal-cloud/internal/deviceauth" "data-terminal-cloud/internal/identity" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgxpool" ) type firmwareIntegrationStore struct { verifiedObject string verifiedDigest string verifiedSize int64 presigned []string removed []string } func (s *firmwareIntegrationStore) PresignUpload(_ context.Context, objectRef string, _ time.Duration) (string, error) { s.presigned = append(s.presigned, objectRef) return "https://objects.invalid/upload/" + objectRef, nil } func (s *firmwareIntegrationStore) PresignDownload(_ context.Context, objectRef string, _ time.Duration, filename string) (string, error) { return "https://objects.invalid/download/" + objectRef + "?filename=" + filename, nil } func (s *firmwareIntegrationStore) VerifyDigest(_ context.Context, objectRef, digest string, size int64) error { s.verifiedObject, s.verifiedDigest, s.verifiedSize = objectRef, digest, size return nil } func (s *firmwareIntegrationStore) Remove(_ context.Context, objectRef string) error { s.removed = append(s.removed, objectRef) return nil } func TestFirmwareReleaseAndRolloutWriteLifecycle(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) owner := identity.Principal{Issuer: testIssuer, Subject: testOwnerSubject} outsider := identity.Principal{Issuer: testIssuer, Subject: testOutsideSubject} householdID := "10000000-0000-0000-0000-000000000211" devicePublicID := "10000000-0000-0000-0000-000000000511" keyID := "firmware-integration-" + uuid.NewString() version := "integration-" + uuid.NewString() var releaseID, retiredReleaseID, replacementReleaseID, rolloutID string defer func() { _, _ = pool.Exec(ctx, `DELETE FROM audit_event WHERE request_id LIKE 'firmware-integration-%'`) if rolloutID != "" { _, _ = pool.Exec(ctx, `DELETE FROM firmware_rollout WHERE public_id=$1::uuid`, rolloutID) } _, _ = 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) } if retiredReleaseID != "" { _, _ = pool.Exec(ctx, `DELETE FROM firmware_release WHERE public_id=$1::uuid`, retiredReleaseID) } if replacementReleaseID != "" { _, _ = pool.Exec(ctx, `DELETE FROM firmware_release WHERE public_id=$1::uuid`, replacementReleaseID) } _, _ = pool.Exec(ctx, `DELETE FROM firmware_signing_key WHERE key_id=$1`, keyID) }() if err := service.RegisterFirmwareSigningKey(ctx, owner, householdID, "firmware-integration-invalid-key", keyID, "invalid"); err == nil { t.Fatal("invalid signing key was accepted") } publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } publicKeyBase64 := base64.StdEncoding.EncodeToString(publicKey) if err := service.RegisterFirmwareSigningKey(ctx, outsider, householdID, "firmware-integration-outsider-key", keyID, publicKeyBase64); !errors.Is(err, ErrAccessDenied) { t.Fatalf("outsider signing key error=%v", err) } if err := service.RegisterFirmwareSigningKey(ctx, owner, householdID, "firmware-integration-key", keyID, publicKeyBase64); err != nil { t.Fatalf("register signing key: %v", err) } if err := service.RegisterFirmwareSigningKey(ctx, owner, householdID, "firmware-integration-key-replay", keyID, publicKeyBase64); err != nil { t.Fatalf("replay same signing key: %v", err) } otherPublicKey, _, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } if err := service.RegisterFirmwareSigningKey(ctx, owner, householdID, "firmware-integration-key-overwrite", keyID, base64.StdEncoding.EncodeToString(otherPublicKey)); !errors.Is(err, ErrFirmwareSigningKeyConflict) { t.Fatalf("overwrite signing key error=%v", err) } keys, err := service.ListFirmwareSigningKeys(ctx, owner, householdID) expectedFingerprint := sha256.Sum256(publicKey) if err != nil || len(keys) != 1 || keys[0].KeyID != keyID || keys[0].Status != "ACTIVE" || keys[0].FingerprintSHA256 != hex.EncodeToString(expectedFingerprint[:]) { t.Fatalf("signing keys=%+v err=%v", keys, err) } firmwareBytes := []byte("synthetic signed firmware image") digestBytes := sha256.Sum256(firmwareBytes) 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(firmwareBytes)))))) release, err := service.PrepareFirmwareRelease(ctx, owner, householdID, "firmware-integration-prepare", PrepareFirmwareInput{ Model: model, HardwareRevision: hardware, Version: version, ExpectedDigest: digest, Signature: signature, SigningKeyID: keyID, MinimumSecurityVersion: "1", ReleaseNotes: "合成固件集成测试", ExpectedSize: int64(len(firmwareBytes)), }) if err != nil || release.Status != "UPLOADING" || release.UploadURL == "" || release.UploadUntil == nil { t.Fatalf("prepared release=%+v err=%v", release, err) } releaseID = release.ID if _, err := service.RevokeFirmwareSigningKey(ctx, owner, householdID, "firmware-integration-revoke-uploading", keyID); !errors.Is(err, ErrFirmwareSigningKeyInUse) { t.Fatalf("revoke uploading signing key error=%v", err) } keys, err = service.ListFirmwareSigningKeys(ctx, owner, householdID) if err != nil || len(keys) != 1 || keys[0].UploadingReleases != 1 { t.Fatalf("uploading key references=%+v err=%v", keys, err) } if !strings.Contains(release.UploadURL, "/firmware/simulated-terminal/sim-rev-a/") { t.Fatalf("unexpected upload URL %q", release.UploadURL) } if _, err := service.RenewFirmwareUpload(ctx, outsider, release.ID, "firmware-integration-outsider-renew"); !errors.Is(err, ErrAccessDenied) { t.Fatalf("outsider renew upload error=%v", err) } renewed, err := service.RenewFirmwareUpload(ctx, owner, release.ID, "firmware-integration-renew") if err != nil || renewed.ID != release.ID || renewed.Status != "UPLOADING" || renewed.UploadURL == "" || renewed.UploadUntil == nil || len(store.presigned) != 2 { t.Fatalf("renewed release=%+v presigned=%v err=%v", renewed, store.presigned, err) } releases, err := service.ListFirmwareReleases(ctx, owner, householdID) if err != nil || len(releases) != 1 || releases[0].ID != release.ID { t.Fatalf("firmware releases=%+v err=%v", releases, err) } if _, err := service.FinalizeFirmwareRelease(ctx, outsider, release.ID, "firmware-integration-outsider-finalize"); !errors.Is(err, ErrAccessDenied) { t.Fatalf("outsider finalize error=%v", err) } release, err = service.FinalizeFirmwareRelease(ctx, owner, release.ID, "firmware-integration-finalize") if err != nil || release.Status != "READY" || release.VerifiedAt == nil { t.Fatalf("finalized release=%+v err=%v", release, err) } if store.verifiedDigest != digest || store.verifiedSize != int64(len(firmwareBytes)) || store.verifiedObject == "" { t.Fatalf("object verification=%+v", store) } replayedRelease, err := service.FinalizeFirmwareRelease(ctx, owner, release.ID, "firmware-integration-finalize-replay") if err != nil || replayedRelease.Status != "READY" { t.Fatalf("replayed finalize=%+v err=%v", replayedRelease, err) } if _, err := service.RenewFirmwareUpload(ctx, owner, release.ID, "firmware-integration-renew-ready"); !errors.Is(err, ErrFirmwareStateConflict) { t.Fatalf("renew READY release error=%v", err) } if _, err := service.PauseFirmwareRelease(ctx, outsider, release.ID, "firmware-integration-outsider-release-pause"); !errors.Is(err, ErrAccessDenied) { t.Fatalf("outsider pause release error=%v", err) } release, err = service.PauseFirmwareRelease(ctx, owner, release.ID, "firmware-integration-release-pause") if err != nil || release.Status != "PAUSED" { t.Fatalf("paused release=%+v err=%v", release, err) } if replayed, err := service.PauseFirmwareRelease(ctx, owner, release.ID, "firmware-integration-release-pause-replay"); err != nil || replayed.Status != "PAUSED" { t.Fatalf("replayed release pause=%+v err=%v", replayed, err) } if _, err := service.StartFirmwareRollout(ctx, owner, householdID, release.ID, "firmware-integration-paused-release-rollout", "暂停版本禁止下发", 100, []string{devicePublicID}); !errors.Is(err, ErrFirmwareStateConflict) { t.Fatalf("rollout with paused release error=%v", err) } release, err = service.ResumeFirmwareRelease(ctx, owner, release.ID, "firmware-integration-release-resume") if err != nil || release.Status != "READY" { t.Fatalf("resumed release=%+v err=%v", release, err) } if replayed, err := service.ResumeFirmwareRelease(ctx, owner, release.ID, "firmware-integration-release-resume-replay"); err != nil || replayed.Status != "READY" { t.Fatalf("replayed release resume=%+v err=%v", replayed, err) } retireVersion := "retire-" + uuid.NewString() retireSignature := base64.StdEncoding.EncodeToString(ed25519.Sign(privateKey, []byte(firmwareManifest(model, hardware, retireVersion, digest, int64(len(firmwareBytes)))))) retiring, err := service.PrepareFirmwareRelease(ctx, owner, householdID, "firmware-integration-prepare-retire", PrepareFirmwareInput{Model: model, HardwareRevision: hardware, Version: retireVersion, ExpectedDigest: digest, Signature: retireSignature, SigningKeyID: keyID, ExpectedSize: int64(len(firmwareBytes))}) if err != nil { t.Fatalf("prepare retiring release: %v", err) } retiredReleaseID = retiring.ID retired, err := service.RetireFirmwareRelease(ctx, owner, retiring.ID, "firmware-integration-retire") if err != nil || retired.Status != "RETIRED" || len(store.removed) != 1 || store.removed[0] == "" { t.Fatalf("retired release=%+v removed=%v err=%v", retired, store.removed, err) } if replayed, err := service.RetireFirmwareRelease(ctx, owner, retiring.ID, "firmware-integration-retire-replay"); err != nil || replayed.Status != "RETIRED" || len(store.removed) != 2 { t.Fatalf("replayed retire=%+v removed=%v err=%v", replayed, store.removed, err) } if _, err := service.FinalizeFirmwareRelease(ctx, owner, retiring.ID, "firmware-integration-finalize-retired"); !errors.Is(err, ErrFirmwareStateConflict) { t.Fatalf("finalize retired release error=%v", err) } replacement, err := service.PrepareFirmwareRelease(ctx, owner, householdID, "firmware-integration-prepare-replacement", PrepareFirmwareInput{Model: model, HardwareRevision: hardware, Version: retireVersion, ExpectedDigest: digest, Signature: retireSignature, SigningKeyID: keyID, ExpectedSize: int64(len(firmwareBytes))}) if err != nil || replacement.Status != "UPLOADING" || replacement.ID == retiring.ID { t.Fatalf("replacement release=%+v err=%v", replacement, err) } replacementReleaseID = replacement.ID if _, err := service.RetireFirmwareRelease(ctx, owner, replacement.ID, "firmware-integration-retire-replacement"); err != nil { t.Fatalf("retire replacement release: %v", err) } if _, err := service.StartFirmwareRollout(ctx, outsider, householdID, release.ID, "firmware-integration-outsider-rollout", "禁止下发", 100, []string{devicePublicID}); !errors.Is(err, ErrAccessDenied) { t.Fatalf("outsider rollout error=%v", err) } rollout, err := service.StartFirmwareRollout(ctx, owner, householdID, release.ID, "firmware-integration-rollout", "全量合成下发", 100, []string{devicePublicID}) if err != nil || rollout.Status != "RUNNING" || rollout.Targeted != 1 { t.Fatalf("rollout=%+v err=%v", rollout, err) } rolloutID = rollout.ID rollout, err = service.GetFirmwareRollout(ctx, owner, rollout.ID) if err != nil || len(rollout.Assignments) != 1 || rollout.Assignments[0].DeviceID != devicePublicID || rollout.Assignments[0].Status != "ASSIGNED" { t.Fatalf("rollout detail=%+v err=%v", rollout, err) } rollouts, err := service.ListFirmwareRollouts(ctx, owner, householdID) if err != nil || len(rollouts) != 1 || rollouts[0].ID != rollout.ID { t.Fatalf("rollouts=%+v err=%v", rollouts, err) } impact, err := service.GetFirmwareReleaseImpact(ctx, owner, release.ID) if err != nil || impact.ReleaseStatus != "READY" || impact.Rollouts.Total != 1 || impact.Rollouts.Running != 1 || impact.Devices.Total != 1 || impact.Devices.Assigned != 1 || impact.Commands.Queued != 1 || impact.Devices.Observing != 0 { t.Fatalf("initial release impact=%+v err=%v", impact, err) } if _, err := service.GetFirmwareReleaseImpact(ctx, outsider, release.ID); !errors.Is(err, ErrAccessDenied) { t.Fatalf("outsider release impact error=%v", err) } downloadURL, expiresAt, err := service.FirmwareDownloadForDevice(ctx, deviceauth.Identity{ ID: "10000000-0000-0000-0000-000000000501", TenantID: "10000000-0000-0000-0000-000000000001", }, release.ID) if err != nil || downloadURL == "" || time.Until(expiresAt) <= 0 { t.Fatalf("firmware download url=%q expires=%s err=%v", downloadURL, expiresAt, err) } observationCommandID := uuid.NewString() if _, err := pool.Exec(ctx, `INSERT INTO device_command(id,tenant_id,device_id,command_type,payload,status,requested_by,idempotency_key,request_digest,expires_at,delivered_at,acknowledged_at) VALUES($1::uuid,'10000000-0000-0000-0000-000000000001'::uuid,'10000000-0000-0000-0000-000000000502'::uuid,'firmware_update',jsonb_build_object('release_id',$2::text),'ACKNOWLEDGED','10000000-0000-0000-0000-000000000101'::uuid,$3,repeat('a',64),now()+interval '1 hour',now(),now())`, observationCommandID, release.ID, "firmware-impact-"+uuid.NewString()); err != nil { t.Fatalf("insert observation command: %v", err) } if _, err := pool.Exec(ctx, `INSERT INTO firmware_assignment(rollout_id,tenant_id,device_id,command_id,status,progress_percent,last_result,last_reported_at) SELECT ro.id,ro.tenant_id,'10000000-0000-0000-0000-000000000502'::uuid,$2::uuid,'INSTALLING',72,'{"phase":"installing","progress_percent":72}'::jsonb,now() FROM firmware_rollout ro WHERE ro.public_id=$1::uuid`, rollout.ID, observationCommandID); err != nil { t.Fatalf("insert observation assignment: %v", err) } impact, err = service.GetFirmwareReleaseImpact(ctx, owner, release.ID) if err != nil || impact.Devices.Total != 2 || impact.Devices.Installing != 1 || impact.Devices.Observing != 1 || impact.Commands.Queued != 1 || impact.Commands.Acknowledged != 1 || len(impact.ObservationDevices) != 1 || impact.ObservationDevices[0].ObservationReason != "INSTALLATION_IN_PROGRESS" || impact.ObservationDevices[0].ProgressPercent == nil || *impact.ObservationDevices[0].ProgressPercent != 72 { t.Fatalf("active release impact=%+v err=%v", impact, err) } if _, err := service.RetireFirmwareRelease(ctx, owner, release.ID, "firmware-integration-retire-running"); !errors.Is(err, ErrFirmwareReleaseInUse) { t.Fatalf("retire release with running rollout error=%v", err) } if _, err := service.RevokeFirmwareSigningKey(ctx, owner, householdID, "firmware-integration-revoke-running", keyID); !errors.Is(err, ErrFirmwareSigningKeyInUse) { t.Fatalf("revoke key with running rollout error=%v", err) } release, err = service.PauseFirmwareRelease(ctx, owner, release.ID, "firmware-integration-release-pause-running") if err != nil || release.Status != "PAUSED" { t.Fatalf("paused running release=%+v err=%v", release, err) } rollout, err = service.GetFirmwareRollout(ctx, owner, rollout.ID) if err != nil || rollout.Status != "PAUSED" { t.Fatalf("rollout not paused with release=%+v err=%v", rollout, err) } impact, err = service.GetFirmwareReleaseImpact(ctx, owner, release.ID) if err != nil || impact.ReleaseStatus != "PAUSED" || impact.Rollouts.Running != 0 || impact.Rollouts.Paused != 1 || impact.Commands.CancelledBeforeDelivery != 1 || impact.Commands.Acknowledged != 1 || impact.Devices.Observing != 1 || len(impact.ObservationDevices) != 1 { t.Fatalf("paused release impact=%+v err=%v", impact, err) } var cancelledCommands int if err := pool.QueryRow(ctx, `SELECT count(*) FROM device_command WHERE payload->>'release_id'=$1 AND status='CANCELLED'`, release.ID).Scan(&cancelledCommands); err != nil || cancelledCommands != 1 { t.Fatalf("release pause cancelled commands=%d err=%v", cancelledCommands, err) } if _, _, err := service.FirmwareDownloadForDevice(ctx, deviceauth.Identity{ ID: "10000000-0000-0000-0000-000000000501", TenantID: "10000000-0000-0000-0000-000000000001", }, release.ID); !errors.Is(err, ErrAccessDenied) { t.Fatalf("paused rollout download error=%v", err) } if _, err := service.RevokeFirmwareSigningKey(ctx, outsider, householdID, "firmware-integration-outsider-revoke", keyID); !errors.Is(err, ErrAccessDenied) { t.Fatalf("outsider revoke signing key error=%v", err) } revokedKey, err := service.RevokeFirmwareSigningKey(ctx, owner, householdID, "firmware-integration-revoke", keyID) if err != nil || revokedKey.Status != "REVOKED" || revokedKey.RevokedAt == nil || revokedKey.ReadyReleases != 0 || revokedKey.RunningRollouts != 0 { t.Fatalf("revoked signing key=%+v err=%v", revokedKey, err) } if replayedKey, err := service.RevokeFirmwareSigningKey(ctx, owner, householdID, "firmware-integration-revoke-replay", keyID); err != nil || replayedKey.Status != "REVOKED" { t.Fatalf("replayed key revoke=%+v err=%v", replayedKey, err) } if err := service.RegisterFirmwareSigningKey(ctx, owner, householdID, "firmware-integration-reactivate", keyID, publicKeyBase64); !errors.Is(err, ErrFirmwareSigningKeyConflict) { t.Fatalf("reactivate revoked signing key error=%v", err) } if _, err := service.ResumeFirmwareRelease(ctx, owner, release.ID, "firmware-integration-resume-revoked-key"); !errors.Is(err, ErrFirmwareSigningKeyInactive) { t.Fatalf("resume release with revoked signing key error=%v", err) } removedBeforeVerifiedRetire := len(store.removed) release, err = service.RetireFirmwareRelease(ctx, owner, release.ID, "firmware-integration-retire-verified") if err != nil || release.Status != "RETIRED" || len(store.removed) != removedBeforeVerifiedRetire { t.Fatalf("retired verified release=%+v removed=%v err=%v", release, store.removed, err) } if replayed, err := service.RetireFirmwareRelease(ctx, owner, release.ID, "firmware-integration-retire-verified-replay"); err != nil || replayed.Status != "RETIRED" || len(store.removed) != removedBeforeVerifiedRetire { t.Fatalf("replayed verified retire=%+v removed=%v err=%v", replayed, store.removed, err) } if _, err := service.StartFirmwareRollout(ctx, owner, householdID, release.ID, "firmware-integration-revoked-key-rollout", "吊销后禁止", 100, []string{devicePublicID}); !errors.Is(err, ErrFirmwareStateConflict) { t.Fatalf("rollout with revoked signing key error=%v", err) } if _, err := service.PrepareFirmwareRelease(ctx, owner, householdID, "firmware-integration-revoked-key-prepare", PrepareFirmwareInput{Model: model, HardwareRevision: hardware, Version: "revoked-" + uuid.NewString(), ExpectedDigest: digest, Signature: signature, SigningKeyID: keyID, ExpectedSize: int64(len(firmwareBytes))}); !errors.Is(err, ErrFirmwareSigningKeyInactive) { t.Fatalf("prepare with revoked signing key error=%v", err) } }