Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
0ca6190
Align secondary path validation across config load, CLI install, and TUI
tis24dev Mar 13, 2026
24f942f
Align --new-install confirmation flow across CLI and TUI
tis24dev Mar 13, 2026
51e9a66
Align existing backup.env handling across CLI and TUI
tis24dev Mar 13, 2026
62734f4
Fix AGE setup validation and install TUI messaging alignment
tis24dev Mar 13, 2026
18803d5
Align Telegram setup flow across CLI and TUI
tis24dev Mar 13, 2026
94b46e1
Align decrypt secret prompt semantics across CLI and TUI
tis24dev Mar 13, 2026
a10ce5a
Add end-to-end coverage for the production decrypt TUI flow
tis24dev Mar 13, 2026
a7a3df6
Align secondary disable semantics across CLI and TUI
tis24dev Mar 13, 2026
dfa28e0
Align install cron scheduling across CLI and TUI
tis24dev Mar 13, 2026
9550745
Add cron install regression coverage for CLI and TUI
tis24dev Mar 13, 2026
8dfd403
test(orchestrator): stabilize decrypt TUI end-to-end tests
tis24dev Mar 13, 2026
1534acb
fix(install): guard optional bootstrap logging in TUI install flow
tis24dev Mar 14, 2026
0adf76d
fix(newkey): guard success logging when bootstrap is nil
tis24dev Mar 14, 2026
42297e4
fix(decrypt): reject unchanged destination paths in CLI and TUI prompts
tis24dev Mar 14, 2026
2f7c89a
refactor: simplify ticker wait in rollback countdown
tis24dev Mar 14, 2026
7046236
fix: respect configured recipient file in --newkey
tis24dev Mar 14, 2026
0fb0f6d
test(tui): complete coverage for new install and telegram setup
tis24dev Mar 14, 2026
bf2b606
test(tui): add missing wizard coverage
tis24dev Mar 14, 2026
1327eab
test(tui): harden Telegram setup TUI tests
tis24dev Mar 14, 2026
66d2623
fix(storage): correct UsedSpace and stop deriving it from Bavail
tis24dev Mar 17, 2026
c9c78c3
fix(orchestrator): avoid duplicate file close in bundle and restore p…
tis24dev Mar 17, 2026
994c11e
fix(orchestrator): make AGE recipient overwrite flow safe and atomic
tis24dev Mar 17, 2026
0fedcbe
fix(restore): extract regular files atomically
tis24dev Mar 18, 2026
0e779d3
fix(config): align notification defaults and legacy env compatibility
tis24dev Mar 18, 2026
10fa090
storage: bound cloud retry backoff and remove overflow-prone shifts
tis24dev Mar 18, 2026
82a4610
Fix binary integrity validation flow
tis24dev Mar 18, 2026
1266a0b
Fix GFS daily minimum normalization across retention paths
tis24dev Mar 18, 2026
c2307d5
notify: honor context cancellation in email, relay, and webhook paths
tis24dev Mar 18, 2026
b26805f
fixstorage: normalize bundle paths in cloud and secondary store flows
tis24dev Mar 18, 2026
fa3bede
Fix restore extraction metadata handling on FakeFS
tis24dev Mar 18, 2026
55878e1
fix(tui): honor canceled context in age prompts
tis24dev Mar 18, 2026
fabf55f
test(storage): allow one-block drift in used space assertion
tis24dev Mar 18, 2026
44c6464
fix(storage): normalize secondary bundle source path
tis24dev Mar 18, 2026
5a60c5c
storage: make cloud retry backoff context-aware
tis24dev Mar 18, 2026
2cd91b0
fix(orchestrator): respect context cancellation in decrypt TUI prompts
tis24dev Mar 18, 2026
8e48538
test: align notification storage fixtures with total space
tis24dev Mar 18, 2026
7e1019c
fix(orchestrator): validate decrypt UI before backup selection
tis24dev Mar 18, 2026
92a5f07
test(orchestrator): increase initial decrypt TUI E2E waits
tis24dev Mar 18, 2026
fe113b4
fix: align secondary path handling with SECONDARY_ENABLED
tis24dev Mar 18, 2026
99cf767
fix(config): normalize legacy notification env aliases in overrides
tis24dev Mar 18, 2026
7abcf2e
test: replace manual env cleanup with t.Setenv
tis24dev Mar 18, 2026
83c991a
test: finish migrating env-based tests to t.Setenv
tis24dev Mar 18, 2026
8f16c01
fix(security): close TOCTOU gap in executable integrity check
tis24dev Mar 18, 2026
408f5be
Verify each finding against the current code and only fix it if neede…
tis24dev Mar 18, 2026
3373234
fix(orchestrator): write backup bundles atomically
tis24dev Mar 18, 2026
d34ee79
fix(install): make new-install reset safe when bootstrap logger is nil
tis24dev Mar 18, 2026
9fb4fcf
fix(storage): avoid duplicate cloud uploads when bundle exists
tis24dev Mar 18, 2026
06b36c4
fix(wizard): guard nil install data with exported sentinel error
tis24dev Mar 18, 2026
adee7df
test(orchestrator): track all created bundle temp files
tis24dev Mar 18, 2026
d1949a5
Harden secondary storage bundle-copy coverage.
tis24dev Mar 18, 2026
90c5ff6
fix(restore): log all temp file cleanup failures
tis24dev Mar 18, 2026
1be079f
test(orchestrator): stop timed TUI key injection on cleanup
tis24dev Mar 18, 2026
46af7da
test(orchestrator): rely on context timeout in decrypt TUI helper
tis24dev Mar 18, 2026
d443f46
test(tui): shorten telegram setup backstop timeout
tis24dev Mar 18, 2026
e04bbbb
refactor(orchestrator): remove redundant form index guard
tis24dev Mar 18, 2026
d02750e
test(storage): expand retention normalization coverage
tis24dev Mar 18, 2026
7c13d03
fix(tui): avoid double slash in preserved entry formatting
tis24dev Mar 18, 2026
b910b17
fix(orchestrator): ignore blank TUI new-path input before cleaning
tis24dev Mar 18, 2026
cf8a37f
test(cmd): make newkey stdout capture cleanup-safe
tis24dev Mar 18, 2026
26ea804
test(cmd): fail fast on telegram CLI skip-path regressions
tis24dev Mar 18, 2026
bfca674
fix(cmd): cap telegram CLI verification retries
tis24dev Mar 18, 2026
b6e52f8
Refactor TUI screen builders and unify shared screen shells
tis24dev Mar 19, 2026
80f0b3d
Harden FakeFS path mapping and unify TUI screen shells
tis24dev Mar 19, 2026
813c27f
Harden test sandbox paths and stabilize decrypt TUI e2e waits
tis24dev Mar 19, 2026
73fd01d
Align decrypt TUI abort startup waits with success flow
tis24dev Mar 19, 2026
39c5611
Simplify BootstrapLogger Flush nil handling
tis24dev Mar 19, 2026
5acc13d
Deduplicate preserved entries fixture in new install tests
tis24dev Mar 19, 2026
1d2d216
Use fd-based permission fixes in binary integrity checks
tis24dev Mar 19, 2026
82b2efb
Deduplicate bundle fixture setup in bundle tests
tis24dev Mar 19, 2026
3cf4332
Deduplicate age wizard runner setup in adapter tests
tis24dev Mar 19, 2026
9a45b25
Centralize AGE setup type constants in wizard flow
tis24dev Mar 19, 2026
33e30eb
Clarify cancellation timing in TUI test
tis24dev Mar 19, 2026
fc15dea
Centralize decrypt TUI E2E mutex in helper
tis24dev Mar 19, 2026
5c5792e
Fix indentation in age setup wizard
tis24dev Mar 19, 2026
45a6dcd
Escape TUI text for safe display
tis24dev Mar 19, 2026
1079852
Preserve surrounding spaces in decrypt prompt
tis24dev Mar 19, 2026
f855e80
Add context-aware RunWithContext to App
tis24dev Mar 19, 2026
32ee697
Pass context to age wizard runner
tis24dev Mar 19, 2026
098f9e4
Escape header text in BuildScreen
tis24dev Mar 19, 2026
61d5dfb
Use context-aware TUI runners
tis24dev Mar 19, 2026
8bc35ca
Propagate context to TUI run functions
tis24dev Mar 19, 2026
c2799a4
Propagate contexts for identity and file ops
tis24dev Mar 19, 2026
b76830c
Escape TUI status messages with tview.Escape
tis24dev Mar 19, 2026
c7e1586
Escape bracketed text in restore TUI prompts
tis24dev Mar 19, 2026
f2398bc
Remove redundant SkipConfigWizard assignment
tis24dev Mar 19, 2026
d42c843
Propagate context to new-install TUI
tis24dev Mar 19, 2026
6cd2b53
Improve testability and add HA/new-install tests
tis24dev Mar 19, 2026
1deee99
Only append slash for existing directories
tis24dev Mar 19, 2026
8d5f2ec
deps(deps): bump golang.org/x/crypto from 0.48.0 to 0.49.0 (#174)
dependabot[bot] Mar 19, 2026
b7878af
Set PATH empty in TestExtendPathIdempotent
tis24dev Mar 19, 2026
adee171
Reject non-regular config path
tis24dev Mar 19, 2026
0da86b9
Merge branch 'dev' of https://github.com/tis24dev/proxsave into dev
tis24dev Mar 19, 2026
45846dc
refactor(main): remove redundant continue in rollback countdown loop
tis24dev Mar 19, 2026
e3efc17
test(tui): broaden primitive text detection in screen tests
tis24dev Mar 19, 2026
c54e7ea
test(tui): wait for app startup before stopping RunWithContext
tis24dev Mar 19, 2026
752d22d
test(orchestrator): always clean up stdout capture pipes
tis24dev Mar 19, 2026
eaaf97b
test(config): run secondary path subtests in parallel
tis24dev Mar 19, 2026
85bd95d
refactor(orchestrator): remove duplicate path cleaning in TUI decrypt…
tis24dev Mar 19, 2026
6dd1251
test: share age setup UI stub across cmd and orchestrator tests
tis24dev Mar 19, 2026
b934704
refactor(install): remove unused detectTelegramCode wrapper
tis24dev Mar 19, 2026
22f3129
fix(telegram): sanitize registration status before CLI output
tis24dev Mar 19, 2026
aeccbb5
fix(identity): propagate legacy upgrade write errors during detection
tis24dev Mar 19, 2026
4d9215a
test(notify): make email relay retry counters atomic
tis24dev Mar 19, 2026
3b4a965
fix(orchestrator): normalize TUI age setup abort errors
tis24dev Mar 19, 2026
b3b9171
test(orchestrator): register TUI e2e rollback cleanup immediately
tis24dev Mar 19, 2026
f052ab6
fix(orchestrator): make recipient backup filenames collision-resistant
tis24dev Mar 19, 2026
e8043f7
fix(notification): warn on nonzero usage with unknown total capacity
tis24dev Mar 19, 2026
a1bb73f
fix(restore-tui): wait for countdown goroutine on RunWithContext errors
tis24dev Mar 19, 2026
bc80537
fix(restore): use directory temp files for atomic restores
tis24dev Mar 19, 2026
ec32ef4
fix(restore): open extracted directories before applying final mode
tis24dev Mar 19, 2026
0f5ceb2
test(tui): add timeout and cleanup path to simulation draw handshake
tis24dev Mar 19, 2026
3ab9464
test(storage): remove flaky mount-wide used space assertion
tis24dev Mar 19, 2026
edf4bcd
fix(tui-wizard): pass caller context into AGE confirmation dialogs
tis24dev Mar 19, 2026
8cc6209
fix(tui-wizard): default existing-config modal focus to safe action
tis24dev Mar 19, 2026
4d181f1
fix(tui-wizard): propagate install context to existing-config modal
tis24dev Mar 19, 2026
87cb057
fix(tui-wizard): resolve preserved entries against new-install base dir
tis24dev Mar 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions cmd/proxsave/encryption_setup.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"context"
"errors"
"fmt"
"io"

"github.com/tis24dev/proxsave/internal/config"
"github.com/tis24dev/proxsave/internal/logging"
"github.com/tis24dev/proxsave/internal/orchestrator"
"github.com/tis24dev/proxsave/internal/types"
)

type encryptionSetupResult struct {
Config *config.Config
RecipientPath string
WroteRecipientFile bool
ReusedExistingRecipients bool
}

func runInitialEncryptionSetupWithUI(ctx context.Context, configPath string, ui orchestrator.AgeSetupUI) (*encryptionSetupResult, error) {
cfg, err := config.LoadConfig(configPath)
if err != nil {
return nil, fmt.Errorf("failed to reload configuration after install: %w", err)
}

logger := logging.New(types.LogLevelError, false)
logger.SetOutput(io.Discard)

orch := orchestrator.New(logger, false)
orch.SetConfig(cfg)

var setupResult *orchestrator.AgeRecipientSetupResult
if ui != nil {
setupResult, err = orch.EnsureAgeRecipientsReadyWithUIDetails(ctx, ui)
} else {
setupResult, err = orch.EnsureAgeRecipientsReadyWithDetails(ctx)
}
if err != nil {
if errors.Is(err, orchestrator.ErrAgeRecipientSetupAborted) {
return nil, fmt.Errorf("encryption setup aborted by user: %w", errInteractiveAborted)
}
return nil, fmt.Errorf("encryption setup failed: %w", err)
}

result := &encryptionSetupResult{Config: cfg}
if setupResult != nil {
result.RecipientPath = setupResult.RecipientPath
result.WroteRecipientFile = setupResult.WroteRecipientFile
result.ReusedExistingRecipients = setupResult.ReusedExistingRecipients
}

return result, nil
}

func runInitialEncryptionSetup(ctx context.Context, configPath string) error {
_, err := runInitialEncryptionSetupWithUI(ctx, configPath, nil)
return err
}
228 changes: 228 additions & 0 deletions cmd/proxsave/encryption_setup_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package main

import (
"context"
"os"
"path/filepath"
"testing"

"filippo.io/age"

"github.com/tis24dev/proxsave/internal/orchestrator"
"github.com/tis24dev/proxsave/internal/testutil"
)

type testAgeSetupUI = testutil.AgeSetupUIStub[orchestrator.AgeRecipientDraft]

func TestRunInitialEncryptionSetupWithUIReloadsConfig(t *testing.T) {
id, err := age.GenerateX25519Identity()
if err != nil {
t.Fatalf("GenerateX25519Identity: %v", err)
}

baseDir := t.TempDir()
configPath := filepath.Join(baseDir, "env", "backup.env")
if err := os.MkdirAll(filepath.Dir(configPath), 0o700); err != nil {
t.Fatalf("MkdirAll: %v", err)
}

content := "BASE_DIR=" + baseDir + "\nENCRYPT_ARCHIVE=true\nAGE_RECIPIENT=" + id.Recipient().String() + "\n"
if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil {
t.Fatalf("WriteFile: %v", err)
}

result, err := runInitialEncryptionSetupWithUI(context.Background(), configPath, nil)
if err != nil {
t.Fatalf("runInitialEncryptionSetupWithUI error: %v", err)
}
if result == nil || result.Config == nil {
t.Fatalf("expected config result")
}
if len(result.Config.AgeRecipients) != 1 || result.Config.AgeRecipients[0] != id.Recipient().String() {
t.Fatalf("AgeRecipients=%v; want [%s]", result.Config.AgeRecipients, id.Recipient().String())
}
if !result.ReusedExistingRecipients {
t.Fatalf("expected ReusedExistingRecipients=true")
}
if result.WroteRecipientFile {
t.Fatalf("expected WroteRecipientFile=false")
}
if result.RecipientPath != "" {
t.Fatalf("RecipientPath=%q; want empty for reuse-only result", result.RecipientPath)
}
}

func TestRunInitialEncryptionSetupWithUIUsesProvidedUI(t *testing.T) {
id, err := age.GenerateX25519Identity()
if err != nil {
t.Fatalf("GenerateX25519Identity: %v", err)
}

baseDir := t.TempDir()
configPath := filepath.Join(baseDir, "env", "backup.env")
if err := os.MkdirAll(filepath.Dir(configPath), 0o700); err != nil {
t.Fatalf("MkdirAll: %v", err)
}

content := "BASE_DIR=" + baseDir + "\nENCRYPT_ARCHIVE=true\n"
if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil {
t.Fatalf("WriteFile: %v", err)
}

ui := &testAgeSetupUI{
AbortErr: orchestrator.ErrAgeRecipientSetupAborted,
Drafts: []*orchestrator.AgeRecipientDraft{
{Kind: orchestrator.AgeRecipientInputExisting, PublicKey: id.Recipient().String()},
},
AddMore: []bool{false},
}

result, err := runInitialEncryptionSetupWithUI(context.Background(), configPath, ui)
if err != nil {
t.Fatalf("runInitialEncryptionSetupWithUI error: %v", err)
}

expectedPath := filepath.Join(baseDir, "identity", "age", "recipient.txt")
if result == nil || result.Config == nil {
t.Fatalf("expected setup result with config")
}
if result.RecipientPath != expectedPath {
t.Fatalf("RecipientPath=%q; want %q", result.RecipientPath, expectedPath)
}
if !result.WroteRecipientFile {
t.Fatalf("expected WroteRecipientFile=true")
}
if result.ReusedExistingRecipients {
t.Fatalf("expected ReusedExistingRecipients=false")
}
if _, err := os.Stat(expectedPath); err != nil {
t.Fatalf("expected recipient file at %s: %v", expectedPath, err)
}
}

func TestRunInitialEncryptionSetupWithUIReusesExistingFileWithoutReportingWrite(t *testing.T) {
id, err := age.GenerateX25519Identity()
if err != nil {
t.Fatalf("GenerateX25519Identity: %v", err)
}

baseDir := t.TempDir()
recipientPath := filepath.Join(baseDir, "identity", "age", "recipient.txt")
if err := os.MkdirAll(filepath.Dir(recipientPath), 0o700); err != nil {
t.Fatalf("MkdirAll: %v", err)
}
if err := os.WriteFile(recipientPath, []byte(id.Recipient().String()+"\n"), 0o600); err != nil {
t.Fatalf("WriteFile(%s): %v", recipientPath, err)
}

configPath := filepath.Join(baseDir, "env", "backup.env")
if err := os.MkdirAll(filepath.Dir(configPath), 0o700); err != nil {
t.Fatalf("MkdirAll(%s): %v", filepath.Dir(configPath), err)
}
content := "BASE_DIR=" + baseDir + "\nENCRYPT_ARCHIVE=true\nAGE_RECIPIENT_FILE=" + recipientPath + "\n"
if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil {
t.Fatalf("WriteFile(%s): %v", configPath, err)
}

result, err := runInitialEncryptionSetupWithUI(context.Background(), configPath, nil)
if err != nil {
t.Fatalf("runInitialEncryptionSetupWithUI error: %v", err)
}

if result == nil || result.Config == nil {
t.Fatalf("expected setup result with config")
}
if !result.ReusedExistingRecipients {
t.Fatalf("expected ReusedExistingRecipients=true")
}
if result.WroteRecipientFile {
t.Fatalf("expected WroteRecipientFile=false")
}
if result.RecipientPath != "" {
t.Fatalf("RecipientPath=%q; want empty for reuse-only result", result.RecipientPath)
}
}

func TestRunNewKeySetupKeepsDefaultRecipientPathContract(t *testing.T) {
id, err := age.GenerateX25519Identity()
if err != nil {
t.Fatalf("GenerateX25519Identity: %v", err)
}

baseDir := t.TempDir()
configPath := filepath.Join(baseDir, "env", "backup.env")
ui := &testAgeSetupUI{
AbortErr: orchestrator.ErrAgeRecipientSetupAborted,
Overwrite: true,
Drafts: []*orchestrator.AgeRecipientDraft{
{Kind: orchestrator.AgeRecipientInputExisting, PublicKey: id.Recipient().String()},
},
AddMore: []bool{false},
}

recipientPath, err := runNewKeySetup(context.Background(), configPath, baseDir, nil, ui)
if err != nil {
t.Fatalf("runNewKeySetup error: %v", err)
}

target := filepath.Join(baseDir, "identity", "age", "recipient.txt")
if recipientPath != target {
t.Fatalf("recipientPath=%q; want %q", recipientPath, target)
}
content, err := os.ReadFile(target)
if err != nil {
t.Fatalf("ReadFile(%s): %v", target, err)
}
if got := string(content); got != id.Recipient().String()+"\n" {
t.Fatalf("content=%q; want %q", got, id.Recipient().String()+"\n")
}
}

func TestRunNewKeySetupUsesConfiguredRecipientFile(t *testing.T) {
id, err := age.GenerateX25519Identity()
if err != nil {
t.Fatalf("GenerateX25519Identity: %v", err)
}

baseDir := t.TempDir()
configPath := filepath.Join(baseDir, "env", "backup.env")
if err := os.MkdirAll(filepath.Dir(configPath), 0o700); err != nil {
t.Fatalf("MkdirAll(%s): %v", filepath.Dir(configPath), err)
}

customPath := filepath.Join(baseDir, "custom", "recipient.txt")
content := "BASE_DIR=" + baseDir + "\nENCRYPT_ARCHIVE=true\nAGE_RECIPIENT_FILE=" + customPath + "\n"
if err := os.WriteFile(configPath, []byte(content), 0o600); err != nil {
t.Fatalf("WriteFile(%s): %v", configPath, err)
}

ui := &testAgeSetupUI{
AbortErr: orchestrator.ErrAgeRecipientSetupAborted,
Overwrite: true,
Drafts: []*orchestrator.AgeRecipientDraft{
{Kind: orchestrator.AgeRecipientInputExisting, PublicKey: id.Recipient().String()},
},
AddMore: []bool{false},
}

recipientPath, err := runNewKeySetup(context.Background(), configPath, baseDir, nil, ui)
if err != nil {
t.Fatalf("runNewKeySetup error: %v", err)
}
if recipientPath != customPath {
t.Fatalf("recipientPath=%q; want %q", recipientPath, customPath)
}

customContent, err := os.ReadFile(customPath)
if err != nil {
t.Fatalf("ReadFile(%s): %v", customPath, err)
}
if got := string(customContent); got != id.Recipient().String()+"\n" {
t.Fatalf("content=%q; want %q", got, id.Recipient().String()+"\n")
}

defaultPath := filepath.Join(baseDir, "identity", "age", "recipient.txt")
if _, err := os.Stat(defaultPath); !os.IsNotExist(err) {
t.Fatalf("default path %s should not be written, stat err=%v", defaultPath, err)
}
}
51 changes: 49 additions & 2 deletions cmd/proxsave/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,55 @@ func TestInputMapInputError(t *testing.T) {
func TestValidateFutureFeatures_SecondaryWithoutPath(t *testing.T) {
cfg := &config.Config{SecondaryEnabled: true}

if err := validateFutureFeatures(cfg); err == nil {
t.Error("expected error for secondary enabled without path")
err := validateFutureFeatures(cfg)
if err == nil {
t.Fatal("expected error for secondary enabled without path")
}
if got, want := err.Error(), "SECONDARY_PATH is required when SECONDARY_ENABLED=true"; got != want {
t.Fatalf("validateFutureFeatures error = %q, want %q", got, want)
}
}

func TestValidateFutureFeatures_SecondaryRejectsRemotePath(t *testing.T) {
cfg := &config.Config{
SecondaryEnabled: true,
SecondaryPath: "remote:path",
}

err := validateFutureFeatures(cfg)
if err == nil {
t.Fatal("expected error for remote-style secondary path")
}
if got, want := err.Error(), "SECONDARY_PATH must be an absolute local filesystem path"; got != want {
t.Fatalf("validateFutureFeatures error = %q, want %q", got, want)
}
}

func TestValidateFutureFeatures_SecondaryAllowsEmptyLogPath(t *testing.T) {
cfg := &config.Config{
SecondaryEnabled: true,
SecondaryPath: "/backup/secondary",
SecondaryLogPath: "",
}

if err := validateFutureFeatures(cfg); err != nil {
t.Fatalf("expected empty secondary log path to be allowed, got %v", err)
}
}

func TestValidateFutureFeatures_SecondaryRejectsInvalidLogPath(t *testing.T) {
cfg := &config.Config{
SecondaryEnabled: true,
SecondaryPath: "/backup/secondary",
SecondaryLogPath: "remote:/logs",
}

err := validateFutureFeatures(cfg)
if err == nil {
t.Fatal("expected error for invalid secondary log path")
}
if got, want := err.Error(), "SECONDARY_LOG_PATH must be an absolute local filesystem path"; got != want {
t.Fatalf("validateFutureFeatures error = %q, want %q", got, want)
}
}

Expand Down
Loading
Loading