Close diagnostic and restore coverage gaps

This commit is contained in:
2026-08-11 12:28:55 +00:00
parent 8ef6e99d69
commit a2a144dffa
4 changed files with 289 additions and 12 deletions

View File

@@ -357,7 +357,7 @@ func TestExecuteRestoreLockConflictFailsAndWritesNothing(t *testing.T) {
}
}
func TestExecuteRestoreInvalidManifestDoesNotCorruptExistingManifest(t *testing.T) {
func TestExecuteRestorePlanInvalidManifestDoesNotCorruptExistingManifest(t *testing.T) {
cfg := restorePlanConfig(t)
store := &storage.FakeBackend{}
current := seedCommittedRestoreSnapshot(t, cfg, store, "20260519T010203Z-a1b2c3d4", map[string][]byte{
@@ -417,6 +417,87 @@ func TestExecuteRestoreInvalidManifestDoesNotCorruptExistingManifest(t *testing.
}
}
func TestExecuteRestoreInvalidManifestReportsFailureAndRetainsRecoveryMarker(t *testing.T) {
workspaceRoot := t.TempDir()
pipelinePath, campaignPath, sessionPath := writeValidConfigFiles(t, workspaceRoot)
cfg, err := config.LoadWithSessionOptions(pipelinePath, campaignPath, sessionPath, config.SessionLoadOptions{})
if err != nil {
t.Fatalf("LoadWithSessionOptions() error = %v", err)
}
store := &storage.FakeBackend{}
seedCommittedRestoreSnapshot(t, cfg, store, "20260519T010203Z-a1b2c3d4", map[string][]byte{
"transcripts/full.json": []byte("remote-transcript"),
})
sessionRoot := artifacts.SessionWorkDirForCampaign(workspaceRoot, cfg.Session.Campaign, cfg.Session.SessionID)
existing := manifest.New(cfg.Session.SessionID, nowUTC())
existing.Campaign = cfg.Session.Campaign
existingPath := filepath.Join(sessionRoot, "manifest.json")
manifestStore := &manifest.LocalStore{}
if err := manifestStore.Save(context.Background(), existingPath, existing); err != nil {
t.Fatalf("save existing local manifest: %v", err)
}
existingData, err := os.ReadFile(existingPath)
if err != nil {
t.Fatalf("read existing local manifest: %v", err)
}
restoreWithStoreAndRealPhases(t, store)
realBuildRestorePlan := buildRestorePlanFn
buildRestorePlanFn = func(ctx context.Context, cfg *config.Config, current *RemoteCurrentState, objectStore storage.ObjectStore, opts RestorePlanOptions) (*RestorePlan, error) {
plan, err := realBuildRestorePlan(ctx, cfg, current, objectStore, opts)
if err != nil {
return nil, err
}
invalidManifest := []byte("{invalid json")
for index := range plan.Actions {
if plan.Actions[index].LocalRelativePath != config.PathManifestFile {
continue
}
plan.Actions[index].VerifiedContent = invalidManifest
plan.Actions[index].SHA256 = restoreCommitSHA256(invalidManifest)
plan.Actions[index].Size = int64(len(invalidManifest))
store.SeedObject(storage.FakeObject{
Key: plan.Actions[index].RemoteKey,
Data: invalidManifest,
ETag: plan.Actions[index].Generation,
})
return plan, nil
}
t.Fatal("restore plan has no manifest action")
return nil, nil
}
var stdout bytes.Buffer
var stderr bytes.Buffer
code := Execute([]string{"session", "restore", cfg.Session.SessionID, "--config", pipelinePath, "--campaign-file", campaignPath, "--session", sessionPath, "--force"}, &stdout, &stderr)
if code == 0 {
t.Fatal("exit code = 0, want non-zero")
}
if !strings.Contains(stderr.String(), "validate manifest decode") {
t.Fatalf("stderr = %q, want manifest validation failure", stderr.String())
}
mustReadEquals(t, filepath.Join(sessionRoot, "transcripts", "full.json"), "remote-transcript")
report := mustReadRestoreReport(t, filepath.Join(sessionRoot, "reports", "restore-latest.json"))
if report.Status != "failed" {
t.Fatalf("report status = %q, want failed", report.Status)
}
if strings.TrimSpace(report.Error) == "" {
t.Fatal("report error is empty, want failure context")
}
afterData, err := os.ReadFile(existingPath)
if err != nil {
t.Fatalf("read local manifest after failure: %v", err)
}
if string(afterData) != string(existingData) {
t.Fatalf("local manifest changed after failed restore; before=%q after=%q", string(existingData), string(afterData))
}
markerPath := artifacts.SessionRestoreMarkerPathForCampaign(workspaceRoot, cfg.Session.Campaign, cfg.Session.SessionID)
if _, err := os.Stat(markerPath); err != nil {
t.Fatalf("incomplete restore marker should remain after failed forced restore: %v", err)
}
}
func TestExecuteRestorePlanPathMismatchFails(t *testing.T) {
cfg := restorePlanConfig(t)
current := restorePlanCurrentState(t, cfg)