120 lines
5.6 KiB
Go
120 lines
5.6 KiB
Go
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)
|
||
|
|
}
|
||
|
|
}
|