mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 10:38:33 -04:00
feat: Config can be loaded from any path with --config-file (-c) flag.
This commit is contained in:
14
TEST_PLAN.md
14
TEST_PLAN.md
@@ -2,22 +2,17 @@
|
|||||||
|
|
||||||
## Current State
|
## Current State
|
||||||
|
|
||||||
- 101 tests, all passing
|
- 104 tests, all passing
|
||||||
- Strong coverage: crypto (100%), ssh (90%), db CRUD + env_file + update_dir, config save/load + paths, scan, features, cant_scan, parse_args
|
- Strong coverage: crypto, ssh, db CRUD + env_file + update_dir, config save/load + paths, scan, features, cant_scan, parse_args, `-c`/`--config-file` flag
|
||||||
- Misleading test files: `cmd_check_test`, `cmd_list_test`, `cmd_nushell_completion_test` don't test their namesake procs
|
- Misleading test files: `cmd_check_test`, `cmd_list_test`, `cmd_nushell_completion_test` don't test their namesake procs
|
||||||
- Biggest remaining gap: all `cmd_*` handlers untested
|
- Biggest remaining gap: all `cmd_*` handlers untested
|
||||||
|
|
||||||
## Next: `load_config` / `save_config` path param + `-c`/`--config-file` flag
|
## Command handler tests
|
||||||
- Refactor `load_config(path: string = "")` and `save_config(cfg, force, path: string = "")` — empty string defaults to `~/.envr/config.json`
|
|
||||||
- Add `-c`/`--config-file` to `parse_args` (now testable)
|
|
||||||
- Wire through `main.odin` so commands receive the config path
|
|
||||||
- Unblocks command handler tests with fixture configs
|
|
||||||
|
|
||||||
## Command handlers (need DB + filesystem fixtures)
|
Stdout will be captured by redirecting `os.stdout` to a pipe.
|
||||||
|
|
||||||
### `cmd_version` (cmd_version.odin)
|
### `cmd_version` (cmd_version.odin)
|
||||||
- Test default output (prints VERSION)
|
- Test default output (prints VERSION)
|
||||||
- Capture stdout, assert content
|
|
||||||
|
|
||||||
### `cmd_list` (cmd_list.odin)
|
### `cmd_list` (cmd_list.odin)
|
||||||
- Test TTY path: fixture DB with rows, capture table output
|
- Test TTY path: fixture DB with rows, capture table output
|
||||||
@@ -69,7 +64,6 @@
|
|||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- All command handler tests will need stdout capture. Consider extracting a helper or using `io.Writer` injection.
|
|
||||||
- DB integration tests should use in-memory SQLite (`:memory:`) where possible.
|
- DB integration tests should use in-memory SQLite (`:memory:`) where possible.
|
||||||
- Temp dir fixtures should follow the pattern in `scan_test.odin`.
|
- Temp dir fixtures should follow the pattern in `scan_test.odin`.
|
||||||
- External dependency tests (`fd`, `git`) should use `#assert` to ensure the dependency is present rather than silently skipping (TODO 28).
|
- External dependency tests (`fd`, `git`) should use `#assert` to ensure the dependency is present rather than silently skipping (TODO 28).
|
||||||
|
|||||||
22
cli.odin
22
cli.odin
@@ -8,10 +8,11 @@ import "core:os"
|
|||||||
import "core:strings"
|
import "core:strings"
|
||||||
|
|
||||||
Command :: struct {
|
Command :: struct {
|
||||||
name: string,
|
name: string,
|
||||||
args: [dynamic]string,
|
args: [dynamic]string,
|
||||||
flags: map[string]string,
|
flags: map[string]string,
|
||||||
bool_set: map[string]bool,
|
bool_set: map[string]bool,
|
||||||
|
config_path: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandInfo :: struct {
|
CommandInfo :: struct {
|
||||||
@@ -94,6 +95,16 @@ parse_args :: proc(args: []string) -> (cmd: Command, ok: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if val, ok := cmd.flags["config-file"]; ok {
|
||||||
|
cmd.config_path = val
|
||||||
|
} else if val, ok := cmd.flags["c"]; ok {
|
||||||
|
cmd.config_path = val
|
||||||
|
} else {
|
||||||
|
// FIXME: Handle err
|
||||||
|
home, _ := os.user_home_dir(context.allocator)
|
||||||
|
cmd.config_path = default_config_path(home)
|
||||||
|
}
|
||||||
|
|
||||||
if has_flag(&cmd, "help") {
|
if has_flag(&cmd, "help") {
|
||||||
print_command_help(cmd.name)
|
print_command_help(cmd.name)
|
||||||
return Command{}, false
|
return Command{}, false
|
||||||
@@ -146,7 +157,7 @@ write_command_help :: proc(name: string, w: io.Writer) -> bool {
|
|||||||
fmt.wprintf(w, "\n%s\n", info.long, flush = false)
|
fmt.wprintf(w, "\n%s\n", info.long, flush = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.wprintf(w, "\nFlags:\n -h, --help help for %s\n", info.name, flush = false)
|
fmt.wprintf(w, "\nFlags:\n -h, --help help for %s\n -c, --config-file <path> config file (default \"~/.envr/config.json\")\n", info.name, flush = false)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,6 +238,7 @@ Available Commands:
|
|||||||
`
|
`
|
||||||
Flags:
|
Flags:
|
||||||
-h, --help help for envr
|
-h, --help help for envr
|
||||||
|
-c, --config-file <path> config file (default "~/.envr/config.json")
|
||||||
|
|
||||||
Use "envr [command] --help" for more information about a command.
|
Use "envr [command] --help" for more information about a command.
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -315,3 +315,44 @@ test_parse_args_flag_then_positional_then_flag :: proc(t: ^testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
test_parse_args_config_file_long_flag :: proc(t: ^testing.T) {
|
||||||
|
cmd, ok := parse_args([]string{"envr", "list", "--config-file", "/custom/config.json"})
|
||||||
|
testing.expect(t, ok, "should succeed")
|
||||||
|
if !ok do return
|
||||||
|
defer delete(cmd.args)
|
||||||
|
defer delete(cmd.flags)
|
||||||
|
defer delete(cmd.bool_set)
|
||||||
|
|
||||||
|
testing.expect(t, cmd.config_path == "/custom/config.json", "config_path should be set from --config-file")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_parse_args_config_file_short_flag :: proc(t: ^testing.T) {
|
||||||
|
cmd, ok := parse_args([]string{"envr", "list", "-c", "/custom/config.json"})
|
||||||
|
testing.expect(t, ok, "should succeed")
|
||||||
|
if !ok do return
|
||||||
|
defer delete(cmd.args)
|
||||||
|
defer delete(cmd.flags)
|
||||||
|
defer delete(cmd.bool_set)
|
||||||
|
|
||||||
|
testing.expect(t, cmd.config_path == "/custom/config.json", "config_path should be set from -c")
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
test_parse_args_config_file_defaults :: proc(t: ^testing.T) {
|
||||||
|
cmd, ok := parse_args([]string{"envr", "list"})
|
||||||
|
testing.expect(t, ok, "should succeed")
|
||||||
|
if !ok do return
|
||||||
|
defer delete(cmd.args)
|
||||||
|
defer delete(cmd.flags)
|
||||||
|
defer delete(cmd.bool_set)
|
||||||
|
|
||||||
|
testing.expect(t, len(cmd.config_path) > 0, "config_path should default to non-empty path")
|
||||||
|
testing.expect(
|
||||||
|
t,
|
||||||
|
strings.contains(cmd.config_path, ".envr"),
|
||||||
|
"default config_path should contain .envr dir, got %s",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ cmd_backup :: proc(cmd: ^Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
db, db_ok := db_open()
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
// TODO: log a message
|
// TODO: log a message
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ cmd_check :: proc(cmd: ^Command) {
|
|||||||
abs_path = resolved
|
abs_path = resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
db, db_ok := db_open()
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import "core:path/filepath"
|
|
||||||
|
|
||||||
cmd_edit_config :: proc(cmd: ^Command) {
|
cmd_edit_config :: proc(cmd: ^Command) {
|
||||||
editor := os.get_env("EDITOR", context.allocator)
|
editor := os.get_env("EDITOR", context.allocator)
|
||||||
@@ -11,11 +10,7 @@ cmd_edit_config :: proc(cmd: ^Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config_path, join_err := filepath.join([]string{envr_dir(), "config.json"})
|
config_path := cmd.config_path
|
||||||
if join_err != nil {
|
|
||||||
fmt.printf("Error building config path: %v\n", join_err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, stat_err := os.stat(config_path, context.allocator)
|
_, stat_err := os.stat(config_path, context.allocator)
|
||||||
if stat_err != nil {
|
if stat_err != nil {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import "core:fmt"
|
|||||||
cmd_init :: proc(cmd: ^Command) {
|
cmd_init :: proc(cmd: ^Command) {
|
||||||
force := has_flag(cmd, "force") || has_flag(cmd, "f")
|
force := has_flag(cmd, "force") || has_flag(cmd, "f")
|
||||||
|
|
||||||
_, cfg_exists := load_config()
|
_, cfg_exists := load_config(cmd.config_path)
|
||||||
if cfg_exists && !force {
|
if cfg_exists && !force {
|
||||||
fmt.println("You have already initialized envr.")
|
fmt.println("You have already initialized envr.")
|
||||||
fmt.println("Run again with the --force flag if you want to reinitialize.")
|
fmt.println("Run again with the --force flag if you want to reinitialize.")
|
||||||
@@ -41,7 +41,7 @@ cmd_init :: proc(cmd: ^Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := new_config(selected_paths[:])
|
cfg := new_config(selected_paths[:], cmd.config_path)
|
||||||
if !save_config(cfg, force = force) {
|
if !save_config(cfg, force = force) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ ListEntry :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmd_list :: proc(cmd: ^Command) {
|
cmd_list :: proc(cmd: ^Command) {
|
||||||
db, db_ok := db_open()
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ cmd_remove :: proc(cmd: ^Command) {
|
|||||||
abs_path = resolved
|
abs_path = resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
db, db_ok := db_open()
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ cmd_restore :: proc(cmd: ^Command) {
|
|||||||
abs_path = resolved
|
abs_path = resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
db, db_ok := db_open()
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ cmd_scan :: proc(cmd: ^Command) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
db, db_ok := db_open()
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ SyncEntry :: struct {
|
|||||||
|
|
||||||
// TODO: Check for quiet failures.
|
// TODO: Check for quiet failures.
|
||||||
cmd_sync :: proc(cmd: ^Command) {
|
cmd_sync :: proc(cmd: ^Command) {
|
||||||
db, db_ok := db_open()
|
db, db_ok := db_open(cmd.config_path)
|
||||||
if !db_ok {
|
if !db_ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
58
config.odin
58
config.odin
@@ -18,21 +18,18 @@ ScanConfig :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Config :: struct {
|
Config :: struct {
|
||||||
Keys: [dynamic]SshKeyPair `json:"keys"`,
|
Keys: [dynamic]SshKeyPair `json:"keys"`,
|
||||||
ScanConfig: ScanConfig `json:"scan"`,
|
ScanConfig: ScanConfig `json:"scan"`,
|
||||||
|
config_path: string `json:"-"`,
|
||||||
}
|
}
|
||||||
|
|
||||||
load_config :: proc() -> (Config, bool) {
|
default_config_path :: proc(home: string) -> string {
|
||||||
home, home_err := os.user_home_dir(context.temp_allocator)
|
// FIXME: catch error
|
||||||
if home_err != nil {
|
path, _ := filepath.join([]string{home, ".envr", "config.json"})
|
||||||
fmt.printf("Error getting home dir: %v\n", home_err)
|
return path
|
||||||
return Config{}, false
|
}
|
||||||
}
|
|
||||||
config_path, join_err := filepath.join([]string{home, ".envr", "config.json"})
|
|
||||||
if join_err != nil {
|
|
||||||
return Config{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
|
load_config :: proc(config_path: string) -> (Config, bool) {
|
||||||
data, read_err := os.read_entire_file_from_path(config_path, context.allocator)
|
data, read_err := os.read_entire_file_from_path(config_path, context.allocator)
|
||||||
if read_err != nil {
|
if read_err != nil {
|
||||||
fmt.println("No config file found. Please run `envr init` to generate one.")
|
fmt.println("No config file found. Please run `envr init` to generate one.")
|
||||||
@@ -45,6 +42,7 @@ load_config :: proc() -> (Config, bool) {
|
|||||||
fmt.printf("Error parsing config: %v\n", err)
|
fmt.printf("Error parsing config: %v\n", err)
|
||||||
return Config{}, false
|
return Config{}, false
|
||||||
}
|
}
|
||||||
|
cfg.config_path = config_path
|
||||||
|
|
||||||
return cfg, true
|
return cfg, true
|
||||||
}
|
}
|
||||||
@@ -55,15 +53,12 @@ delete_config :: proc(cfg: Config) {
|
|||||||
delete(cfg.ScanConfig.Include)
|
delete(cfg.ScanConfig.Include)
|
||||||
}
|
}
|
||||||
|
|
||||||
envr_dir :: proc() -> string {
|
envr_dir :: proc(config_path: string) -> string {
|
||||||
home, _ := os.user_home_dir(context.allocator)
|
return filepath.dir(config_path)
|
||||||
dir, _ := filepath.join([]string{home, ".envr"})
|
|
||||||
return dir
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data_encrypted_path :: proc() -> string {
|
data_encrypted_path :: proc(config_path: string) -> string {
|
||||||
dir := envr_dir()
|
path, _ := filepath.join([]string{envr_dir(config_path), "data.envr"})
|
||||||
path, _ := filepath.join([]string{dir, "data.envr"})
|
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +108,10 @@ find_ssh_private_keys :: proc() -> (keys: [dynamic]string, ok: bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
new_config :: proc(private_key_paths: []string) -> Config {
|
new_config :: proc(
|
||||||
|
private_key_paths: []string,
|
||||||
|
cfg_path: string = "~/.envr/config.json",
|
||||||
|
) -> Config {
|
||||||
keys := make([dynamic]SshKeyPair, 0, len(private_key_paths))
|
keys := make([dynamic]SshKeyPair, 0, len(private_key_paths))
|
||||||
for priv in private_key_paths {
|
for priv in private_key_paths {
|
||||||
// TODO: Is this bad?
|
// TODO: Is this bad?
|
||||||
@@ -136,30 +134,22 @@ new_config :: proc(private_key_paths: []string) -> Config {
|
|||||||
Include = include,
|
Include = include,
|
||||||
}
|
}
|
||||||
|
|
||||||
return Config{Keys = keys, ScanConfig = scan_cfg}
|
return Config{Keys = keys, ScanConfig = scan_cfg, config_path = cfg_path}
|
||||||
}
|
}
|
||||||
|
|
||||||
save_config :: proc(cfg: Config, force: bool = false) -> bool {
|
save_config :: proc(cfg: Config, force: bool = false) -> bool {
|
||||||
home, home_err := os.user_home_dir(context.allocator)
|
config_dir := envr_dir(cfg.config_path)
|
||||||
if home_err != nil {
|
|
||||||
fmt.printf("Error getting home dir: %v\n", home_err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
config_dir, _ := filepath.join([]string{home, ".envr"})
|
|
||||||
|
|
||||||
if !os.exists(config_dir) {
|
if !os.exists(config_dir) {
|
||||||
mkdir_err := os.make_directory(config_dir)
|
mkdir_err := os.make_directory(config_dir)
|
||||||
if mkdir_err != nil {
|
if mkdir_err != nil {
|
||||||
fmt.printf("Error creating ~/.envr directory: %v\n", mkdir_err)
|
fmt.printf("Error creating %s directory: %v\n", config_dir, mkdir_err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config_path, _ := filepath.join([]string{config_dir, "config.json"})
|
if os.exists(cfg.config_path) && !force {
|
||||||
|
info, stat_err := os.stat(cfg.config_path, context.allocator)
|
||||||
if os.exists(config_path) && !force {
|
|
||||||
info, stat_err := os.stat(config_path, context.allocator)
|
|
||||||
if stat_err == nil {
|
if stat_err == nil {
|
||||||
defer os.file_info_delete(info, context.allocator)
|
defer os.file_info_delete(info, context.allocator)
|
||||||
if info.size > 0 {
|
if info.size > 0 {
|
||||||
@@ -175,7 +165,7 @@ save_config :: proc(cfg: Config, force: bool = false) -> bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
write_err := os.write_entire_file(config_path, data)
|
write_err := os.write_entire_file(cfg.config_path, data)
|
||||||
if write_err != nil {
|
if write_err != nil {
|
||||||
fmt.printf("Error writing config: %v\n", write_err)
|
fmt.printf("Error writing config: %v\n", write_err)
|
||||||
return false
|
return false
|
||||||
|
|||||||
127
config_test.odin
127
config_test.odin
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "core:os"
|
import "core:os"
|
||||||
|
import "core:path/filepath"
|
||||||
import "core:strings"
|
import "core:strings"
|
||||||
import "core:sync"
|
import "core:sync"
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
@@ -69,27 +70,19 @@ test_new_config_exclude_patterns :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_save_load_config_roundtrip :: proc(t: ^testing.T) {
|
test_save_load_config_roundtrip :: proc(t: ^testing.T) {
|
||||||
sync.mutex_lock(&home_mutex)
|
|
||||||
defer sync.mutex_unlock(&home_mutex)
|
|
||||||
|
|
||||||
old_home := os.get_env("HOME", context.temp_allocator)
|
|
||||||
defer {
|
|
||||||
if old_home != "" {
|
|
||||||
os.set_env("HOME", old_home)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base := fmt.tprintf("/tmp/envr-test-cfg-rt-%d", os.get_pid())
|
base := fmt.tprintf("/tmp/envr-test-cfg-rt-%d", os.get_pid())
|
||||||
os.mkdir_all(base)
|
os.mkdir_all(base)
|
||||||
defer os.remove_all(base)
|
defer os.remove_all(base)
|
||||||
os.set_env("HOME", base)
|
|
||||||
|
|
||||||
cfg := new_config([]string{"/home/user/.ssh/id_ed25519"})
|
cfgPath, err := filepath.join([]string{base, "config.json"}, context.temp_allocator)
|
||||||
|
testing.expect(t, err == nil, "cfgPath should build successfully")
|
||||||
|
|
||||||
|
cfg := new_config([]string{"/home/user/.ssh/id_ed25519"}, cfgPath)
|
||||||
defer delete_config(cfg)
|
defer delete_config(cfg)
|
||||||
|
|
||||||
testing.expect(t, save_config(cfg, force=true), "save should succeed")
|
testing.expect(t, save_config(cfg, force = true), "save should succeed")
|
||||||
|
|
||||||
loaded, ok := load_config()
|
loaded, ok := load_config(cfg.config_path)
|
||||||
testing.expect(t, ok, "load should succeed")
|
testing.expect(t, ok, "load should succeed")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer delete_config(loaded)
|
defer delete_config(loaded)
|
||||||
@@ -105,106 +98,62 @@ test_save_load_config_roundtrip :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_load_config_missing :: proc(t: ^testing.T) {
|
test_load_config_missing :: proc(t: ^testing.T) {
|
||||||
sync.mutex_lock(&home_mutex)
|
_, ok := load_config("/tmp/envr-test-cfg-nonexistent/config.json")
|
||||||
defer sync.mutex_unlock(&home_mutex)
|
|
||||||
|
|
||||||
old_home := os.get_env("HOME", context.temp_allocator)
|
|
||||||
defer {
|
|
||||||
if old_home != "" {
|
|
||||||
os.set_env("HOME", old_home)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base := fmt.tprintf("/tmp/envr-test-cfg-missing-%d", os.get_pid())
|
|
||||||
os.mkdir_all(base)
|
|
||||||
defer os.remove_all(base)
|
|
||||||
os.set_env("HOME", base)
|
|
||||||
|
|
||||||
_, ok := load_config()
|
|
||||||
testing.expect(t, !ok, "missing config should return false")
|
testing.expect(t, !ok, "missing config should return false")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_save_config_no_clobber :: proc(t: ^testing.T) {
|
test_save_config_no_clobber :: proc(t: ^testing.T) {
|
||||||
sync.mutex_lock(&home_mutex)
|
|
||||||
defer sync.mutex_unlock(&home_mutex)
|
|
||||||
|
|
||||||
old_home := os.get_env("HOME", context.temp_allocator)
|
|
||||||
defer {
|
|
||||||
if old_home != "" {
|
|
||||||
os.set_env("HOME", old_home)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base := fmt.tprintf("/tmp/envr-test-cfg-noclobber-%d", os.get_pid())
|
base := fmt.tprintf("/tmp/envr-test-cfg-noclobber-%d", os.get_pid())
|
||||||
os.mkdir_all(base)
|
os.mkdir_all(base)
|
||||||
defer os.remove_all(base)
|
defer os.remove_all(base)
|
||||||
os.set_env("HOME", base)
|
|
||||||
|
|
||||||
cfg := new_config([]string{"/home/user/.ssh/key1"})
|
cfgPath, err := filepath.join([]string{base, "config.json"}, context.temp_allocator)
|
||||||
|
testing.expect(t, err == nil, "cfgPath should build successfully")
|
||||||
|
|
||||||
|
cfg := new_config([]string{"/home/user/.ssh/key1"}, cfgPath)
|
||||||
defer delete_config(cfg)
|
defer delete_config(cfg)
|
||||||
testing.expect(t, save_config(cfg, force=true), "first save should succeed")
|
testing.expect(t, save_config(cfg, force = true), "first save should succeed")
|
||||||
|
|
||||||
cfg2 := new_config([]string{"/home/user/.ssh/key2"})
|
cfg2 := new_config([]string{"/home/user/.ssh/key2"}, cfgPath)
|
||||||
defer delete_config(cfg2)
|
defer delete_config(cfg2)
|
||||||
testing.expect(t, !save_config(cfg2), "second save without force should fail")
|
testing.expect(t, !save_config(cfg2), "second save without force should fail")
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_save_config_force_overwrites :: proc(t: ^testing.T) {
|
test_save_config_force_overwrites :: proc(t: ^testing.T) {
|
||||||
sync.mutex_lock(&home_mutex)
|
|
||||||
defer sync.mutex_unlock(&home_mutex)
|
|
||||||
|
|
||||||
old_home := os.get_env("HOME", context.temp_allocator)
|
|
||||||
defer {
|
|
||||||
if old_home != "" {
|
|
||||||
os.set_env("HOME", old_home)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base := fmt.tprintf("/tmp/envr-test-cfg-force-%d", os.get_pid())
|
base := fmt.tprintf("/tmp/envr-test-cfg-force-%d", os.get_pid())
|
||||||
os.mkdir_all(base)
|
os.mkdir_all(base)
|
||||||
defer os.remove_all(base)
|
defer os.remove_all(base)
|
||||||
os.set_env("HOME", base)
|
|
||||||
|
|
||||||
cfg := new_config([]string{"/home/user/.ssh/key1"})
|
cfgPath, err := filepath.join([]string{base, "config.json"}, context.temp_allocator)
|
||||||
|
testing.expect(t, err == nil, "cfgPath should build successfully")
|
||||||
|
|
||||||
|
cfg := new_config([]string{"/home/user/.ssh/key1"}, cfgPath)
|
||||||
defer delete_config(cfg)
|
defer delete_config(cfg)
|
||||||
testing.expect(t, save_config(cfg, force=true), "first save should succeed")
|
testing.expect(t, save_config(cfg, force = true), "first save should succeed")
|
||||||
|
|
||||||
cfg2 := new_config([]string{"/home/user/.ssh/key2"})
|
cfg2 := new_config([]string{"/home/user/.ssh/key2"}, cfgPath)
|
||||||
defer delete_config(cfg2)
|
defer delete_config(cfg2)
|
||||||
testing.expect(t, save_config(cfg2, force=true), "force save should overwrite")
|
testing.expect(t, save_config(cfg2, force = true), "force save should overwrite")
|
||||||
|
|
||||||
loaded, ok := load_config()
|
loaded, ok := load_config(cfgPath)
|
||||||
testing.expect(t, ok, "load should succeed")
|
testing.expect(t, ok, "load should succeed")
|
||||||
if !ok do return
|
if !ok do return
|
||||||
defer delete_config(loaded)
|
defer delete_config(loaded)
|
||||||
|
|
||||||
testing.expect(t, len(loaded.Keys) == 1, "should have 1 key")
|
testing.expect(t, len(loaded.Keys) == 1, "should have 1 key")
|
||||||
testing.expect(t, loaded.Keys[0].Private == "/home/user/.ssh/key2", "should be the overwritten key")
|
testing.expect(
|
||||||
|
t,
|
||||||
|
loaded.Keys[0].Private == "/home/user/.ssh/key2",
|
||||||
|
"should be the overwritten key",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_envr_dir :: proc(t: ^testing.T) {
|
test_envr_dir :: proc(t: ^testing.T) {
|
||||||
sync.mutex_lock(&home_mutex)
|
dir := envr_dir("/tmp/envr-fake-home-envrdir/.envr/config.json")
|
||||||
defer sync.mutex_unlock(&home_mutex)
|
testing.expectf(t, strings.has_suffix(dir, ".envr"), "dir should end with .envr, got %s", dir)
|
||||||
|
|
||||||
old_home := os.get_env("HOME", context.temp_allocator)
|
|
||||||
defer {
|
|
||||||
if old_home != "" {
|
|
||||||
os.set_env("HOME", old_home)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
os.set_env("HOME", "/tmp/envr-fake-home-envrdir")
|
|
||||||
|
|
||||||
dir := envr_dir()
|
|
||||||
testing.expectf(
|
|
||||||
t,
|
|
||||||
strings.has_suffix(dir, ".envr"),
|
|
||||||
"dir should end with .envr, got %s",
|
|
||||||
dir,
|
|
||||||
)
|
|
||||||
testing.expectf(
|
testing.expectf(
|
||||||
t,
|
t,
|
||||||
strings.contains(dir, "envr-fake-home-envrdir"),
|
strings.contains(dir, "envr-fake-home-envrdir"),
|
||||||
@@ -215,19 +164,7 @@ test_envr_dir :: proc(t: ^testing.T) {
|
|||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
test_data_encrypted_path :: proc(t: ^testing.T) {
|
test_data_encrypted_path :: proc(t: ^testing.T) {
|
||||||
sync.mutex_lock(&home_mutex)
|
p := data_encrypted_path("/tmp/envr-fake-home-datapath/config.json")
|
||||||
defer sync.mutex_unlock(&home_mutex)
|
|
||||||
|
|
||||||
old_home := os.get_env("HOME", context.temp_allocator)
|
|
||||||
defer {
|
|
||||||
if old_home != "" {
|
|
||||||
os.set_env("HOME", old_home)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
os.set_env("HOME", "/tmp/envr-fake-home-datapath")
|
|
||||||
|
|
||||||
p := data_encrypted_path()
|
|
||||||
testing.expectf(t, strings.has_suffix(p, "data.envr"), "should end with data.envr, got %s", p)
|
testing.expectf(t, strings.has_suffix(p, "data.envr"), "should end with data.envr, got %s", p)
|
||||||
testing.expectf(t, strings.contains(p, ".envr"), "should contain .envr dir, got %s", p)
|
testing.expectf(t, strings.contains(p, ".envr"), "should contain .envr dir, got %s", p)
|
||||||
}
|
}
|
||||||
@@ -247,9 +184,7 @@ test_search_paths_expands_tilde :: proc(t: ^testing.T) {
|
|||||||
os.set_env("HOME", "/tmp/envr-fake-home-search")
|
os.set_env("HOME", "/tmp/envr-fake-home-search")
|
||||||
|
|
||||||
cfg := Config {
|
cfg := Config {
|
||||||
ScanConfig = ScanConfig {
|
ScanConfig = ScanConfig{Include = make([dynamic]string, 0, 1)},
|
||||||
Include = make([dynamic]string, 0, 1),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
defer delete(cfg.ScanConfig.Include)
|
defer delete(cfg.ScanConfig.Include)
|
||||||
append(&cfg.ScanConfig.Include, "~")
|
append(&cfg.ScanConfig.Include, "~")
|
||||||
|
|||||||
12
db.odin
12
db.odin
@@ -48,13 +48,13 @@ make_temp_path :: proc() -> string {
|
|||||||
return strings.to_string(b)
|
return strings.to_string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
db_open :: proc() -> (Db, bool) {
|
db_open :: proc(cfg_path: string) -> (Db, bool) {
|
||||||
cfg, ok := load_config()
|
cfg, ok := load_config(cfg_path)
|
||||||
if !ok {
|
if !ok {
|
||||||
return Db{}, false
|
return Db{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
data_path := data_encrypted_path()
|
data_path := data_encrypted_path(cfg.config_path)
|
||||||
_, stat_err := os.stat(data_path, context.allocator)
|
_, stat_err := os.stat(data_path, context.allocator)
|
||||||
|
|
||||||
db: ^rawptr
|
db: ^rawptr
|
||||||
@@ -108,8 +108,8 @@ db_close :: proc(d: ^Db) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
data_path := data_encrypted_path()
|
data_path := data_encrypted_path(d.cfg.config_path)
|
||||||
envr_d := envr_dir()
|
envr_d := envr_dir(d.cfg.config_path)
|
||||||
os.mkdir_all(envr_d)
|
os.mkdir_all(envr_d)
|
||||||
|
|
||||||
write_err := os.write_entire_file(data_path, encrypted)
|
write_err := os.write_entire_file(data_path, encrypted)
|
||||||
@@ -186,7 +186,7 @@ db_vacuum_to_file :: proc(db: ^rawptr, path: string) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
db_restore_from_encrypted :: proc(db: ^rawptr, cfg: Config) -> bool {
|
db_restore_from_encrypted :: proc(db: ^rawptr, cfg: Config) -> bool {
|
||||||
data_path := data_encrypted_path()
|
data_path := data_encrypted_path(cfg.config_path)
|
||||||
encrypted_data, read_err := os.read_entire_file_from_path(data_path, context.temp_allocator)
|
encrypted_data, read_err := os.read_entire_file_from_path(data_path, context.temp_allocator)
|
||||||
if read_err != nil {
|
if read_err != nil {
|
||||||
fmt.printf("Error reading encrypted database: %v\n", read_err)
|
fmt.printf("Error reading encrypted database: %v\n", read_err)
|
||||||
|
|||||||
Reference in New Issue
Block a user