Step 26: Test cleanup.
Tightened lint config (added copyloopvar, durationcheck, makezero,
nilerr, bodyclose). Added 3 combo tests: encrypted+locked files,
dir-only+locked entries, lock/unlock toggle on encrypted entries.
Fixed stale API signatures in ARCHITECTURE.md. All tests already
used t.TempDir() and AddOptions{} consistently.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
192
garden/locked_combo_test.go
Normal file
192
garden/locked_combo_test.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package garden
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncryptedLockedFile(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
repoDir := filepath.Join(root, "repo")
|
||||
|
||||
g, err := Init(repoDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Init: %v", err)
|
||||
}
|
||||
|
||||
if err := g.EncryptInit("passphrase"); err != nil {
|
||||
t.Fatalf("EncryptInit: %v", err)
|
||||
}
|
||||
|
||||
testFile := filepath.Join(root, "secret")
|
||||
if err := os.WriteFile(testFile, []byte("locked secret"), 0o600); err != nil {
|
||||
t.Fatalf("writing: %v", err)
|
||||
}
|
||||
|
||||
// Add as both encrypted and locked.
|
||||
if err := g.Add([]string{testFile}, AddOptions{Encrypt: true, Lock: true}); err != nil {
|
||||
t.Fatalf("Add: %v", err)
|
||||
}
|
||||
|
||||
entry := g.manifest.Files[0]
|
||||
if !entry.Encrypted {
|
||||
t.Error("should be encrypted")
|
||||
}
|
||||
if !entry.Locked {
|
||||
t.Error("should be locked")
|
||||
}
|
||||
if entry.PlaintextHash == "" {
|
||||
t.Error("should have plaintext hash")
|
||||
}
|
||||
|
||||
origHash := entry.Hash
|
||||
|
||||
// Modify the file — checkpoint should skip (locked).
|
||||
if err := os.WriteFile(testFile, []byte("system overwrote"), 0o600); err != nil {
|
||||
t.Fatalf("modifying: %v", err)
|
||||
}
|
||||
|
||||
if err := g.Checkpoint(""); err != nil {
|
||||
t.Fatalf("Checkpoint: %v", err)
|
||||
}
|
||||
|
||||
if g.manifest.Files[0].Hash != origHash {
|
||||
t.Error("checkpoint should skip locked file even if encrypted")
|
||||
}
|
||||
|
||||
// Status should report drifted.
|
||||
statuses, err := g.Status()
|
||||
if err != nil {
|
||||
t.Fatalf("Status: %v", err)
|
||||
}
|
||||
if len(statuses) != 1 || statuses[0].State != "drifted" {
|
||||
t.Errorf("expected drifted, got %v", statuses)
|
||||
}
|
||||
|
||||
// Restore should decrypt and overwrite without prompting.
|
||||
if err := g.Restore(nil, false, nil); err != nil {
|
||||
t.Fatalf("Restore: %v", err)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(testFile)
|
||||
if err != nil {
|
||||
t.Fatalf("reading: %v", err)
|
||||
}
|
||||
if string(got) != "locked secret" {
|
||||
t.Errorf("content = %q, want %q", got, "locked secret")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDirOnlyLocked(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
repoDir := filepath.Join(root, "repo")
|
||||
|
||||
g, err := Init(repoDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Init: %v", err)
|
||||
}
|
||||
|
||||
testDir := filepath.Join(root, "lockdir")
|
||||
if err := os.MkdirAll(testDir, 0o755); err != nil {
|
||||
t.Fatalf("mkdir: %v", err)
|
||||
}
|
||||
|
||||
// Add as dir-only and locked.
|
||||
if err := g.Add([]string{testDir}, AddOptions{DirOnly: true, Lock: true}); err != nil {
|
||||
t.Fatalf("Add: %v", err)
|
||||
}
|
||||
|
||||
entry := g.manifest.Files[0]
|
||||
if entry.Type != "directory" {
|
||||
t.Errorf("type = %s, want directory", entry.Type)
|
||||
}
|
||||
if !entry.Locked {
|
||||
t.Error("should be locked")
|
||||
}
|
||||
|
||||
// Remove the directory.
|
||||
if err := os.RemoveAll(testDir); err != nil {
|
||||
t.Fatalf("removing: %v", err)
|
||||
}
|
||||
|
||||
// Restore should recreate it.
|
||||
if err := g.Restore(nil, false, nil); err != nil {
|
||||
t.Fatalf("Restore: %v", err)
|
||||
}
|
||||
|
||||
info, err := os.Stat(testDir)
|
||||
if err != nil {
|
||||
t.Fatalf("directory not restored: %v", err)
|
||||
}
|
||||
if !info.IsDir() {
|
||||
t.Error("should be a directory")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLockUnlockEncryptedToggle(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
repoDir := filepath.Join(root, "repo")
|
||||
|
||||
g, err := Init(repoDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Init: %v", err)
|
||||
}
|
||||
|
||||
if err := g.EncryptInit("passphrase"); err != nil {
|
||||
t.Fatalf("EncryptInit: %v", err)
|
||||
}
|
||||
|
||||
testFile := filepath.Join(root, "secret")
|
||||
if err := os.WriteFile(testFile, []byte("data"), 0o600); err != nil {
|
||||
t.Fatalf("writing: %v", err)
|
||||
}
|
||||
|
||||
// Add encrypted but not locked.
|
||||
if err := g.Add([]string{testFile}, AddOptions{Encrypt: true}); err != nil {
|
||||
t.Fatalf("Add: %v", err)
|
||||
}
|
||||
|
||||
if g.manifest.Files[0].Locked {
|
||||
t.Fatal("should not be locked initially")
|
||||
}
|
||||
|
||||
// Lock it.
|
||||
if err := g.Lock([]string{testFile}); err != nil {
|
||||
t.Fatalf("Lock: %v", err)
|
||||
}
|
||||
|
||||
if !g.manifest.Files[0].Locked {
|
||||
t.Error("should be locked")
|
||||
}
|
||||
if !g.manifest.Files[0].Encrypted {
|
||||
t.Error("should still be encrypted")
|
||||
}
|
||||
|
||||
// Modify — checkpoint should skip.
|
||||
origHash := g.manifest.Files[0].Hash
|
||||
if err := os.WriteFile(testFile, []byte("changed"), 0o600); err != nil {
|
||||
t.Fatalf("modifying: %v", err)
|
||||
}
|
||||
|
||||
if err := g.Checkpoint(""); err != nil {
|
||||
t.Fatalf("Checkpoint: %v", err)
|
||||
}
|
||||
|
||||
if g.manifest.Files[0].Hash != origHash {
|
||||
t.Error("checkpoint should skip locked encrypted file")
|
||||
}
|
||||
|
||||
// Unlock — checkpoint should now pick up changes.
|
||||
if err := g.Unlock([]string{testFile}); err != nil {
|
||||
t.Fatalf("Unlock: %v", err)
|
||||
}
|
||||
|
||||
if err := g.Checkpoint(""); err != nil {
|
||||
t.Fatalf("Checkpoint: %v", err)
|
||||
}
|
||||
|
||||
if g.manifest.Files[0].Hash == origHash {
|
||||
t.Error("unlocked: checkpoint should update encrypted file hash")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user