verstak/internal/core/notes/note_path_test.go

138 lines
3.3 KiB
Go

package notes
import (
"os"
"path/filepath"
"testing"
)
func TestAssertContained(t *testing.T) {
tests := []struct {
name string
root string
target string
wantErr bool
}{
{
name: "file inside root",
root: "/tmp/vault/Notes",
target: "/tmp/vault/Notes/test.md",
wantErr: false,
},
{
name: "file at root boundary",
root: "/tmp/vault/Notes",
target: "/tmp/vault/Notes",
wantErr: false,
},
{
name: "path traversal via ..",
root: "/tmp/vault/Notes",
target: "/tmp/vault/Notes/../../../etc/passwd",
wantErr: true,
},
{
name: "path traversal to parent",
root: "/tmp/vault/Notes",
target: "/tmp/vault/other/file.md",
wantErr: true,
},
{
name: "completely different path",
root: "/tmp/vault/Notes",
target: "/etc/passwd",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
root := filepath.Clean(tt.root)
target := filepath.Clean(tt.target)
err := assertContained(root, target)
if (err != nil) != tt.wantErr {
t.Errorf("assertContained(%q, %q) error = %v, wantErr %v", root, target, err, tt.wantErr)
}
})
}
}
func TestAssertContainedSymlinkEscape(t *testing.T) {
dir := t.TempDir()
// Create a real directory structure.
notesDir := filepath.Join(dir, "vault", "Notes")
os.MkdirAll(notesDir, 0o750)
// Create a directory outside the vault.
outsideDir := filepath.Join(dir, "outside")
os.MkdirAll(outsideDir, 0o750)
// Create a file outside the vault.
outsideFile := filepath.Join(outsideDir, "secret.md")
os.WriteFile(outsideFile, []byte("secret"), 0o640)
// Create a symlink inside Notes/ pointing outside.
symlinkPath := filepath.Join(notesDir, "escape.md")
if err := os.Symlink(outsideFile, symlinkPath); err != nil {
t.Skipf("cannot create symlink: %v", err)
}
// assertContained should detect the symlink escape.
err := assertContained(notesDir, symlinkPath)
if err == nil {
t.Error("expected error for symlink escape, got nil")
}
}
func TestAssertContainedNonExistentPath(t *testing.T) {
dir := t.TempDir()
notesDir := filepath.Join(dir, "vault", "Notes")
os.MkdirAll(notesDir, 0o750)
// Non-existent file inside root — should pass.
err := assertContained(notesDir, filepath.Join(notesDir, "newfile.md"))
if err != nil {
t.Errorf("expected no error for non-existent file inside root: %v", err)
}
// Non-existent file outside root — should fail.
err = assertContained(notesDir, filepath.Join(dir, "outside", "file.md"))
if err == nil {
t.Error("expected error for non-existent file outside root")
}
}
func TestNoteFileRoot(t *testing.T) {
tests := []struct {
name string
vaultRoot string
parentFsPath string
want string
}{
{
name: "parentless note",
vaultRoot: "/tmp/vault",
parentFsPath: "",
want: "/tmp/vault/Notes",
},
{
name: "note inside project",
vaultRoot: "/tmp/vault",
parentFsPath: "Projects/MyProject",
want: "/tmp/vault/Projects/MyProject/Notes",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := noteFileRoot(tt.vaultRoot, tt.parentFsPath)
got = filepath.Clean(got)
want := filepath.Clean(tt.want)
if got != want {
t.Errorf("noteFileRoot(%q, %q) = %q, want %q", tt.vaultRoot, tt.parentFsPath, got, want)
}
})
}
}