mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-05-11 20:49:47 +08:00
227 lines
7.3 KiB
Go
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)
|
|
}
|
|
}
|