package main import "core:fmt" import "core:os" import "core:testing" import "sqlite" make_test_db :: proc() -> (Db, bool) { db: ^rawptr rc := sqlite.db_open(":memory:", &db) if rc != sqlite.OK { return Db{}, false } create_sql := "CREATE TABLE IF NOT EXISTS envr_env_files (path TEXT PRIMARY KEY NOT NULL, remotes TEXT, sha256 TEXT NOT NULL, contents TEXT NOT NULL)" rc = sqlite.db_exec(db, string_to_cstring(create_sql), nil, nil, nil) if rc != sqlite.OK { sqlite.db_close(db) return Db{}, false } return Db{db = db}, true } make_test_env_file :: proc(path, sha, contents: string, remotes: []string = {}) -> EnvFile { f := EnvFile { Path = path, Dir = "", Sha256 = sha, contents = contents, Remotes = make([dynamic]string, 0, len(remotes)), } for r in remotes { append(&f.Remotes, r) } return f } @(test) test_db_insert_and_fetch :: proc(t: ^testing.T) { d, ok := make_test_db() testing.expect(t, ok, "failed to create test db") if !ok do return defer sqlite.db_close(d.db) f := make_test_env_file( "/project/.env", "abc123", "SECRET=value", []string{"git@github.com:user/repo.git"}, ) defer delete(f.Remotes) testing.expect(t, db_insert(&d, f), "insert should succeed") fetched, fetch_ok := db_fetch(&d, "/project/.env") testing.expect(t, fetch_ok, "fetch should succeed") if !fetch_ok do return defer delete(fetched.Remotes) testing.expect(t, fetched.Path == "/project/.env", "path mismatch") testing.expect(t, fetched.Sha256 == "abc123", "sha mismatch") testing.expect(t, fetched.contents == "SECRET=value", "contents mismatch") testing.expect(t, len(fetched.Remotes) == 1, "remotes count mismatch") testing.expect(t, fetched.Remotes[0] == "git@github.com:user/repo.git", "remote mismatch") } @(test) test_db_fetch_missing :: proc(t: ^testing.T) { d, ok := make_test_db() testing.expect(t, ok, "failed to create test db") if !ok do return defer sqlite.db_close(d.db) _, fetch_ok := db_fetch(&d, "/nonexistent/.env") testing.expect(t, !fetch_ok, "fetch missing should return false") } @(test) test_db_insert_or_replace :: proc(t: ^testing.T) { d, ok := make_test_db() testing.expect(t, ok, "failed to create test db") if !ok do return defer sqlite.db_close(d.db) f1 := make_test_env_file("/project/.env", "sha1", "KEY=old") defer delete(f1.Remotes) testing.expect(t, db_insert(&d, f1), "first insert should succeed") f2 := make_test_env_file("/project/.env", "sha2", "KEY=new") defer delete(f2.Remotes) testing.expect(t, db_insert(&d, f2), "second insert should succeed") results, list_ok := db_list(&d) testing.expect(t, list_ok, "list should succeed") if !list_ok do return defer delete(results) testing.expect(t, len(results) == 1, "should have 1 row, not 2") fetched, fetch_ok := db_fetch(&d, "/project/.env") testing.expect(t, fetch_ok, "fetch should succeed") if !fetch_ok do return defer delete(fetched.Remotes) testing.expect(t, fetched.contents == "KEY=new", "contents should be updated") testing.expect(t, fetched.Sha256 == "sha2", "sha should be updated") } @(test) test_db_delete_existing :: proc(t: ^testing.T) { d, ok := make_test_db() testing.expect(t, ok, "failed to create test db") if !ok do return defer sqlite.db_close(d.db) f := make_test_env_file("/project/.env", "sha", "KEY=val") defer delete(f.Remotes) db_insert(&d, f) testing.expect(t, db_delete(&d, "/project/.env"), "delete should return true") _, fetch_ok := db_fetch(&d, "/project/.env") testing.expect(t, !fetch_ok, "row should be gone after delete") } @(test) test_db_delete_missing :: proc(t: ^testing.T) { d, ok := make_test_db() testing.expect(t, ok, "failed to create test db") if !ok do return defer sqlite.db_close(d.db) testing.expect(t, !db_delete(&d, "/nonexistent/.env"), "delete missing should return false") } @(test) test_db_list_multiple :: proc(t: ^testing.T) { d, ok := make_test_db() testing.expect(t, ok, "failed to create test db") if !ok do return defer sqlite.db_close(d.db) f1 := make_test_env_file("/proj1/.env", "sha1", "A=1", []string{"git@github.com:a/repo.git"}) f2 := make_test_env_file("/proj2/.env", "sha2", "B=2", []string{"git@github.com:b/repo.git"}) f3 := make_test_env_file("/proj3/.env", "sha3", "C=3") defer delete(f1.Remotes) defer delete(f2.Remotes) defer delete(f3.Remotes) db_insert(&d, f1) db_insert(&d, f2) db_insert(&d, f3) results, list_ok := db_list(&d) testing.expect(t, list_ok, "list should succeed") if !list_ok do return defer delete(results) testing.expect(t, len(results) == 3, "should have 3 rows") } @(test) test_db_list_empty :: proc(t: ^testing.T) { d, ok := make_test_db() testing.expect(t, ok, "failed to create test db") if !ok do return defer sqlite.db_close(d.db) results, list_ok := db_list(&d) testing.expect(t, list_ok, "list should succeed on empty db") testing.expect(t, len(results) == 0, "should have 0 rows") if list_ok do delete(results) } @(test) test_db_insert_sets_changed :: proc(t: ^testing.T) { d, ok := make_test_db() testing.expect(t, ok, "failed to create test db") if !ok do return defer sqlite.db_close(d.db) testing.expect(t, !d.changed, "changed should start false") f := make_test_env_file("/project/.env", "sha", "KEY=val") defer delete(f.Remotes) db_insert(&d, f) testing.expect(t, d.changed, "changed should be true after insert") } @(test) test_db_delete_sets_changed :: proc(t: ^testing.T) { d, ok := make_test_db() testing.expect(t, ok, "failed to create test db") if !ok do return defer sqlite.db_close(d.db) f := make_test_env_file("/project/.env", "sha", "KEY=val") defer delete(f.Remotes) db_insert(&d, f) d.changed = false db_delete(&d, "/project/.env") testing.expect(t, d.changed, "changed should be true after delete") } @(test) test_db_vacuum_to_file :: proc(t: ^testing.T) { d, ok := make_test_db() testing.expect(t, ok, "failed to create test db") if !ok do return defer sqlite.db_close(d.db) f := make_test_env_file("/project/.env", "sha", "KEY=val") defer delete(f.Remotes) db_insert(&d, f) vacuum_path := fmt.tprintf("/tmp/envr-test-vacuum-%d.db", os.get_pid()) defer os.remove(vacuum_path) testing.expect(t, db_vacuum_to_file(d.db, vacuum_path), "vacuum should succeed") _, stat_err := os.stat(vacuum_path, context.allocator) testing.expect(t, stat_err == nil, "vacuumed file should exist") if stat_err != nil do return data, read_err := os.read_entire_file_from_path(vacuum_path, context.allocator) testing.expect(t, read_err == nil, "should read vacuumed file") if read_err != nil do return defer delete(data) testing.expect(t, len(data) > 0, "vacuumed file should be non-empty") } @(test) test_db_update_required_noop :: proc(t: ^testing.T) { testing.expect(t, !db_update_required({}), "Noop should not require update") } @(test) test_db_update_required_backed_up :: proc(t: ^testing.T) { testing.expect(t, db_update_required({.BackedUp}), "BackedUp should require update") } @(test) test_db_update_required_dir_updated :: proc(t: ^testing.T) { testing.expect(t, db_update_required({.DirUpdated}), "DirUpdated should require update") } @(test) test_db_update_required_restored :: proc(t: ^testing.T) { testing.expect(t, !db_update_required({.Restored}), "Restored alone should not require update") } @(test) test_db_update_required_error :: proc(t: ^testing.T) { testing.expect(t, !db_update_required({.Error}), "Error alone should not require update") } @(test) test_db_update_required_combined :: proc(t: ^testing.T) { combined := SyncFlag{.DirUpdated, .Restored} testing.expect(t, db_update_required(combined), "DirUpdated|Restored should require update") } @(test) test_shares_remote_overlap :: proc(t: ^testing.T) { f := EnvFile { Remotes = make([dynamic]string, 2, context.temp_allocator), } append(&f.Remotes, "git@github.com:user/repo.git") append(&f.Remotes, "git@gitlab.com:user/repo.git") remotes := []string{"git@github.com:user/repo.git"} testing.expect(t, shares_remote(&f, remotes), "should share remote") } @(test) test_shares_remote_no_overlap :: proc(t: ^testing.T) { f := EnvFile { Remotes = make([dynamic]string, 1, context.temp_allocator), } append(&f.Remotes, "git@github.com:user/repo.git") remotes := []string{"git@github.com:other/repo.git"} testing.expect(t, !shares_remote(&f, remotes), "should not share remote") } @(test) test_shares_remote_empty_file_remotes :: proc(t: ^testing.T) { f := EnvFile { Remotes = make([dynamic]string, 0, context.temp_allocator), } remotes := []string{"git@github.com:user/repo.git"} testing.expect(t, !shares_remote(&f, remotes), "empty file remotes should not share") } @(test) test_shares_remote_empty_check_remotes :: proc(t: ^testing.T) { f := EnvFile { Remotes = make([dynamic]string, 1, context.temp_allocator), } append(&f.Remotes, "git@github.com:user/repo.git") remotes: []string testing.expect(t, !shares_remote(&f, remotes), "empty check remotes should not share") } @(test) test_shares_remote_both_empty :: proc(t: ^testing.T) { f := EnvFile { Remotes = make([dynamic]string, 0), } remotes: []string testing.expect(t, !shares_remote(&f, remotes), "both empty should not share") }