Files
MyGoNavi/internal/app/security_update_state_test.go
2026-04-10 21:29:45 +08:00

227 lines
7.3 KiB
Go

package app
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
func TestSecurityUpdateStateStartRoundCreatesMarkerAndManifest(t *testing.T) {
repo := newSecurityUpdateStateRepository(t.TempDir())
status, err := repo.StartRound(StartSecurityUpdateRequest{
SourceType: SecurityUpdateSourceTypeCurrentAppSavedConfig,
})
if err != nil {
t.Fatalf("StartRound returned error: %v", err)
}
if status.MigrationID == "" {
t.Fatal("expected migration ID to be created")
}
if status.SourceType != SecurityUpdateSourceTypeCurrentAppSavedConfig {
t.Fatalf("expected source type %q, got %q", SecurityUpdateSourceTypeCurrentAppSavedConfig, status.SourceType)
}
if status.OverallStatus != SecurityUpdateOverallStatusInProgress {
t.Fatalf("expected overall status %q, got %q", SecurityUpdateOverallStatusInProgress, status.OverallStatus)
}
if !status.BackupAvailable {
t.Fatal("expected backupAvailable=true")
}
markerPath := filepath.Join(repo.configDir, securityUpdateMarkerDirName, securityUpdateMarkerFileName)
if _, err := os.Stat(markerPath); err != nil {
t.Fatalf("expected marker file at %q: %v", markerPath, err)
}
data, err := os.ReadFile(markerPath)
if err != nil {
t.Fatalf("ReadFile marker failed: %v", err)
}
var marker securityUpdateMarker
if err := json.Unmarshal(data, &marker); err != nil {
t.Fatalf("Unmarshal marker failed: %v", err)
}
if marker.MigrationID != status.MigrationID {
t.Fatalf("expected marker migration ID %q, got %q", status.MigrationID, marker.MigrationID)
}
manifestPath := filepath.Join(repo.configDir, securityUpdateBackupRootDirName, status.MigrationID, securityUpdateManifestFileName)
if _, err := os.Stat(manifestPath); err != nil {
t.Fatalf("expected manifest file at %q: %v", manifestPath, err)
}
}
func TestSecurityUpdateStateRetryRoundReusesCurrentMigrationID(t *testing.T) {
repo := newSecurityUpdateStateRepository(t.TempDir())
initial, err := repo.StartRound(StartSecurityUpdateRequest{
SourceType: SecurityUpdateSourceTypeCurrentAppSavedConfig,
})
if err != nil {
t.Fatalf("StartRound returned error: %v", err)
}
initial.OverallStatus = SecurityUpdateOverallStatusNeedsAttention
initial.UpdatedAt = nowRFC3339()
initial.Summary = SecurityUpdateSummary{
Total: 1,
Pending: 1,
}
initial.Issues = []SecurityUpdateIssue{
{
ID: "connection-legacy-1",
Scope: SecurityUpdateIssueScopeConnection,
RefID: "legacy-1",
Title: "Legacy",
Severity: SecurityUpdateIssueSeverityMedium,
Status: SecurityUpdateItemStatusNeedsAttention,
ReasonCode: SecurityUpdateIssueReasonCodeSecretMissing,
Action: SecurityUpdateIssueActionOpenConnection,
Message: "连接密码已丢失,请重新保存后再继续",
},
}
if err := repo.WriteResult(initial); err != nil {
t.Fatalf("WriteResult returned error: %v", err)
}
retried, err := repo.RetryRound(RetrySecurityUpdateRequest{
MigrationID: initial.MigrationID,
})
if err != nil {
t.Fatalf("RetryRound returned error: %v", err)
}
if retried.MigrationID != initial.MigrationID {
t.Fatalf("expected retry to reuse migration ID %q, got %q", initial.MigrationID, retried.MigrationID)
}
entries, err := os.ReadDir(filepath.Join(repo.configDir, securityUpdateBackupRootDirName))
if err != nil {
t.Fatalf("ReadDir backup root failed: %v", err)
}
if len(entries) != 1 {
t.Fatalf("expected retry to keep a single backup directory, got %d", len(entries))
}
}
func TestSecurityUpdateStateRetryRoundRejectsRolledBackRound(t *testing.T) {
repo := newSecurityUpdateStateRepository(t.TempDir())
marker := securityUpdateMarker{
SchemaVersion: securityUpdateSchemaVersion,
MigrationID: "migration-1",
SourceType: SecurityUpdateSourceTypeCurrentAppSavedConfig,
Status: SecurityUpdateOverallStatusRolledBack,
StartedAt: "2026-04-09T00:00:00Z",
UpdatedAt: "2026-04-09T00:05:00Z",
BackupPath: repo.backupPath("migration-1"),
Summary: SecurityUpdateSummary{
Total: 1,
Failed: 1,
},
Issues: []SecurityUpdateIssue{
{
ID: "system-blocked",
Scope: SecurityUpdateIssueScopeSystem,
Title: "安全更新未完成",
Severity: SecurityUpdateIssueSeverityHigh,
Status: SecurityUpdateItemStatusFailed,
ReasonCode: SecurityUpdateIssueReasonCodeEnvironmentBlocked,
Action: SecurityUpdateIssueActionViewDetails,
Message: "当前环境无法完成本次安全更新,请稍后重试",
},
},
}
if err := repo.writeMarker(marker); err != nil {
t.Fatalf("writeMarker returned error: %v", err)
}
if _, err := repo.RetryRound(RetrySecurityUpdateRequest{MigrationID: marker.MigrationID}); err == nil {
t.Fatal("expected RetryRound to reject rolled_back round")
}
current, err := repo.LoadMarker()
if err != nil {
t.Fatalf("LoadMarker returned error: %v", err)
}
if current.OverallStatus != SecurityUpdateOverallStatusRolledBack {
t.Fatalf("expected marker to remain rolled_back, got %q", current.OverallStatus)
}
}
func TestBuildSecurityUpdateStatusDoesNotAllowRetryAfterRollback(t *testing.T) {
status := buildSecurityUpdateStatus(securityUpdateMarker{
SchemaVersion: securityUpdateSchemaVersion,
MigrationID: "migration-1",
SourceType: SecurityUpdateSourceTypeCurrentAppSavedConfig,
Status: SecurityUpdateOverallStatusRolledBack,
StartedAt: "2026-04-09T00:00:00Z",
UpdatedAt: "2026-04-09T00:05:00Z",
BackupPath: filepath.Join("backup", "migration-1"),
Summary: SecurityUpdateSummary{
Total: 1,
Failed: 1,
},
Issues: []SecurityUpdateIssue{
{
ID: "system-blocked",
Scope: SecurityUpdateIssueScopeSystem,
Title: "安全更新未完成",
Severity: SecurityUpdateIssueSeverityHigh,
Status: SecurityUpdateItemStatusFailed,
ReasonCode: SecurityUpdateIssueReasonCodeEnvironmentBlocked,
Action: SecurityUpdateIssueActionViewDetails,
Message: "当前环境无法完成本次安全更新,请稍后重试",
},
},
})
if status.CanRetry {
t.Fatal("expected rolled_back status to require restart instead of retry")
}
if !status.CanStart {
t.Fatal("expected rolled_back status to allow starting a new round")
}
}
func TestSecurityUpdateStateRestartRoundCreatesNewMigrationID(t *testing.T) {
repo := newSecurityUpdateStateRepository(t.TempDir())
initial, err := repo.StartRound(StartSecurityUpdateRequest{
SourceType: SecurityUpdateSourceTypeCurrentAppSavedConfig,
})
if err != nil {
t.Fatalf("StartRound returned error: %v", err)
}
restarted, err := repo.RestartRound(RestartSecurityUpdateRequest{
SourceType: SecurityUpdateSourceTypeCurrentAppSavedConfig,
})
if err != nil {
t.Fatalf("RestartRound returned error: %v", err)
}
if restarted.MigrationID == initial.MigrationID {
t.Fatal("expected restart to create a new migration ID")
}
entries, err := os.ReadDir(filepath.Join(repo.configDir, securityUpdateBackupRootDirName))
if err != nil {
t.Fatalf("ReadDir backup root failed: %v", err)
}
if len(entries) != 2 {
t.Fatalf("expected restart to create a second backup directory, got %d", len(entries))
}
current, err := repo.LoadMarker()
if err != nil {
t.Fatalf("LoadMarker returned error: %v", err)
}
if current.MigrationID != restarted.MigrationID {
t.Fatalf("expected marker to point to latest migration ID %q, got %q", restarted.MigrationID, current.MigrationID)
}
}