// Vikunja is a to-do list application to facilitate your life. // Copyright 2018-present Vikunja and contributors. All rights reserved. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package db import ( "os" "path/filepath" "runtime" "testing" "code.vikunja.io/api/pkg/config" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestResolveDatabasePath(t *testing.T) { // Save original config values originalRootpath := config.ServiceRootpath.GetString() defer config.ServiceRootpath.Set(originalRootpath) t.Run("with explicitly configured rootpath", func(t *testing.T) { // Set a rootpath that is different from the executable location config.ServiceRootpath.Set("/custom/path") result := resolveDatabasePath("vikunja.db") expected := filepath.Join("/custom/path", "vikunja.db") assert.Equal(t, expected, result) }) t.Run("with default rootpath uses user data directory", func(t *testing.T) { // Get the actual executable path and set rootpath to it execPath, err := os.Executable() require.NoError(t, err) config.ServiceRootpath.Set(filepath.Dir(execPath)) result := resolveDatabasePath("vikunja.db") // Result should contain the platform-specific user data directory // and not be in the executable directory assert.NotEqual(t, filepath.Join(filepath.Dir(execPath), "vikunja.db"), result) assert.Contains(t, result, "vikunja.db") // Verify it's using a platform-appropriate path switch runtime.GOOS { case "windows": // Should be in %LOCALAPPDATA%\Vikunja or %USERPROFILE%\AppData\Local\Vikunja assert.Contains(t, result, "Vikunja") case "darwin": // Should be in ~/Library/Application Support/Vikunja assert.Contains(t, result, "Library") assert.Contains(t, result, "Application Support") default: // Should be in ~/.local/share/vikunja or $XDG_DATA_HOME/vikunja assert.NotEqual(t, filepath.Dir(result), filepath.Dir(execPath), "Database should not be in executable directory", ) } }) t.Run("with subdirectory path", func(t *testing.T) { config.ServiceRootpath.Set("/custom/path") result := resolveDatabasePath("data/vikunja.db") expected := filepath.Join("/custom/path", "data", "vikunja.db") assert.Equal(t, expected, result) }) } func TestGetUserDataDir(t *testing.T) { test := func() string { dataDir, err := getUserDataDir() require.NoError(t, err) assert.NotEmpty(t, dataDir) // Verify the directory was created info, err := os.Stat(dataDir) require.NoError(t, err) assert.True(t, info.IsDir()) return dataDir } // Verify platform-specific paths switch runtime.GOOS { case "windows": dataDir := test() assert.Contains(t, dataDir, "Vikunja") case "darwin": dataDir := test() assert.Contains(t, dataDir, "Library") assert.Contains(t, dataDir, "Application Support") assert.Contains(t, dataDir, "Vikunja") default: originalXDGDataHome := os.Getenv("XDG_DATA_HOME") defer func() { if originalXDGDataHome != "" { os.Setenv("XDG_DATA_HOME", originalXDGDataHome) } else { os.Unsetenv("XDG_DATA_HOME") } }() t.Run("with XDG_DATA_HOME", func(t *testing.T) { os.Setenv("XDG_DATA_HOME", "/tmp") dataDir := test() assert.Contains(t, dataDir, filepath.Join("/tmp", "vikunja")) }) t.Run("without XDG_DATA_HOME", func(t *testing.T) { os.Unsetenv("XDG_DATA_HOME") dataDir := test() assert.Contains(t, dataDir, "vikunja") }) } } func TestIsSystemDirectory(t *testing.T) { tests := []struct { name string path string expected bool }{ // Windows system directories { name: "Windows System32", path: "C:\\Windows\\System32\\vikunja.db", expected: runtime.GOOS == "windows", }, { name: "Windows SysWOW64", path: "C:\\Windows\\SysWOW64\\vikunja.db", expected: runtime.GOOS == "windows", }, { name: "Windows root", path: "C:\\Windows\\vikunja.db", expected: runtime.GOOS == "windows", }, { name: "Windows System32 lowercase", path: "c:\\windows\\system32\\vikunja.db", expected: runtime.GOOS == "windows", }, // Unix-like system directories { name: "/bin", path: "/bin/vikunja.db", expected: runtime.GOOS != "windows", }, { name: "/sbin", path: "/sbin/vikunja.db", expected: runtime.GOOS != "windows", }, { name: "/usr/bin", path: "/usr/bin/vikunja.db", expected: runtime.GOOS != "windows", }, { name: "/etc", path: "/etc/vikunja.db", expected: runtime.GOOS != "windows", }, // Non-system directories { name: "user home directory (Unix)", path: "/home/user/vikunja.db", expected: false, }, { name: "user profile directory (Windows)", path: "C:\\Users\\user\\vikunja.db", expected: false, }, { name: "custom directory", path: "/opt/vikunja/vikunja.db", expected: false, }, { name: "relative path", path: "./vikunja.db", expected: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := isSystemDirectory(tt.path) assert.Equal(t, tt.expected, result, "Expected isSystemDirectory(%s) to be %v on %s", tt.path, tt.expected, runtime.GOOS) }) } } func TestIsSystemDirectory_EdgeCases(t *testing.T) { if runtime.GOOS == "windows" { t.Run("false positives - paths containing 'windows' but not system directories", func(t *testing.T) { tests := []struct { name string path string }{ { name: "custom app with windows in path", path: "C:\\myapp\\windows\\data\\vikunja.db", }, { name: "windows directory on non-C drive", path: "D:\\windows\\vikunja.db", }, { name: "user directory named windows", path: "C:\\Users\\windows\\vikunja.db", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.False(t, isSystemDirectory(tt.path)) }) } }) t.Run("safe Windows subdirectories", func(t *testing.T) { assert.False(t, isSystemDirectory("C:\\Windows\\Temp\\vikunja.db")) }) t.Run("actual Windows system directories", func(t *testing.T) { tests := []struct { name string path string }{ { name: "Windows root", path: "C:\\Windows\\vikunja.db", }, { name: "Windows root lowercase", path: "c:\\windows\\vikunja.db", }, { name: "System32", path: "C:\\Windows\\System32\\vikunja.db", }, { name: "System32 uppercase", path: "C:\\WINDOWS\\SYSTEM32\\vikunja.db", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.True(t, isSystemDirectory(tt.path)) }) } }) } else { t.Run("false positives - paths containing system dir names", func(t *testing.T) { tests := []struct { name string path string }{ { name: "/home/bin not same as /bin", path: "/home/bin/vikunja.db", }, { name: "/opt/sbin not same as /sbin", path: "/opt/sbin/vikunja.db", }, { name: "/usr/local/bin is safe", path: "/usr/local/bin/vikunja.db", }, { name: "/binaries not same as /bin", path: "/binaries/vikunja.db", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.False(t, isSystemDirectory(tt.path)) }) } }) t.Run("actual Unix system directories", func(t *testing.T) { tests := []struct { name string path string }{ { name: "/bin", path: "/bin/vikunja.db", }, { name: "/etc", path: "/etc/vikunja.db", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.True(t, isSystemDirectory(tt.path)) }) } }) } }