Implement remote current-state discovery for the restore subcommand

This commit is contained in:
2026-05-19 21:49:19 -05:00
parent 02ab106ade
commit 128449040f
4 changed files with 456 additions and 7 deletions

View File

@@ -2,12 +2,16 @@ package app
import (
"bytes"
"context"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"gitea.maximumdirect.net/eric/narratio/internal/adapters/storage"
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
"gitea.maximumdirect.net/eric/narratio/internal/config"
)
func TestExecuteRestoreHelp(t *testing.T) {
@@ -31,6 +35,23 @@ func TestExecuteRestoreHelp(t *testing.T) {
}
func TestExecuteRestoreRecognizedAndReturnsNYI(t *testing.T) {
origStoreFn := newObjectStoreFromConfigFn
origDiscoverFn := discoverRemoteCurrentStateFn
t.Cleanup(func() {
newObjectStoreFromConfigFn = origStoreFn
discoverRemoteCurrentStateFn = origDiscoverFn
})
newObjectStoreFromConfigFn = func(context.Context, *config.Config) (storage.ObjectStore, error) {
return &storage.FakeBackend{}, nil
}
discoverRemoteCurrentStateFn = func(context.Context, *config.Config, storage.ObjectStore) (*RemoteCurrentState, error) {
return &RemoteCurrentState{
SessionID: "2026-05-03",
Campaign: "sample-campaign",
RunID: "20260519T010203Z-a1b2c3d4",
}, nil
}
workspaceRoot := t.TempDir()
pipelinePath, sessionPath := writeValidConfigFiles(t, workspaceRoot)
@@ -50,11 +71,14 @@ func TestExecuteRestoreRecognizedAndReturnsNYI(t *testing.T) {
&stderr,
)
if code == 0 {
t.Fatal("exit code = 0, want non-zero (phase 2 NYI)")
t.Fatal("exit code = 0, want non-zero (phase 3 NYI boundary)")
}
errText := stderr.String()
if !strings.Contains(errText, "restore: not yet implemented (phase 3: remote current-state discovery)") {
t.Fatalf("stderr = %q, want NYI error", errText)
if !strings.Contains(errText, "discovered remote current state for sample-campaign/2026-05-03 (run 20260519T010203Z-a1b2c3d4)") {
t.Fatalf("stderr = %q, want discovery summary context", errText)
}
if !strings.Contains(errText, "not yet implemented (phase 3: remote current-state discovery)") {
t.Fatalf("stderr = %q, want phase-3 NYI marker", errText)
}
if strings.Contains(errText, "unknown command") {
t.Fatalf("stderr = %q, restore should be recognized command", errText)
@@ -62,7 +86,7 @@ func TestExecuteRestoreRecognizedAndReturnsNYI(t *testing.T) {
manifestPath := artifacts.SessionManifestPathForCampaign(workspaceRoot, "sample-campaign", "2026-05-03")
if _, err := os.Stat(manifestPath); !os.IsNotExist(err) {
t.Fatalf("manifest should not be created during phase-2 restore preflight; stat err=%v", err)
t.Fatalf("manifest should not be created during phase-3 restore discovery; stat err=%v", err)
}
}
@@ -82,6 +106,13 @@ func TestExecuteRestoreRejectsUnexpectedPositionalArguments(t *testing.T) {
}
func TestExecuteRestoreFailsWhenStorageBackendNotConfigured(t *testing.T) {
origStoreFn := newObjectStoreFromConfigFn
origDiscoverFn := discoverRemoteCurrentStateFn
t.Cleanup(func() {
newObjectStoreFromConfigFn = origStoreFn
discoverRemoteCurrentStateFn = origDiscoverFn
})
workspaceRoot := t.TempDir()
pipelinePath, sessionPath := writeRestoreConfigWithoutStorage(t, workspaceRoot)
@@ -96,6 +127,34 @@ func TestExecuteRestoreFailsWhenStorageBackendNotConfigured(t *testing.T) {
}
}
func TestExecuteRestoreDiscoveryErrorSurfaced(t *testing.T) {
origStoreFn := newObjectStoreFromConfigFn
origDiscoverFn := discoverRemoteCurrentStateFn
t.Cleanup(func() {
newObjectStoreFromConfigFn = origStoreFn
discoverRemoteCurrentStateFn = origDiscoverFn
})
newObjectStoreFromConfigFn = func(context.Context, *config.Config) (storage.ObjectStore, error) {
return &storage.FakeBackend{}, nil
}
discoverRemoteCurrentStateFn = func(context.Context, *config.Config, storage.ObjectStore) (*RemoteCurrentState, error) {
return nil, fmt.Errorf("remote current run pointer missing: %q", "dnd/campaigns/sample-campaign/sessions/2026-05-03/current/run_id.txt")
}
workspaceRoot := t.TempDir()
pipelinePath, sessionPath := writeValidConfigFiles(t, workspaceRoot)
var stdout bytes.Buffer
var stderr bytes.Buffer
code := Execute([]string{"restore", "--config", pipelinePath, "--session", sessionPath}, &stdout, &stderr)
if code == 0 {
t.Fatal("exit code = 0, want non-zero")
}
if !strings.Contains(stderr.String(), "remote current run pointer missing") {
t.Fatalf("stderr = %q, want discovery error context", stderr.String())
}
}
func writeRestoreConfigWithoutStorage(t *testing.T, workspaceRoot string) (string, string) {
t.Helper()