mirror of
https://github.com/sbrow/envr.git
synced 2026-06-27 10:38:33 -04:00
336 lines
8.4 KiB
Odin
336 lines
8.4 KiB
Odin
#+test
|
|
package main
|
|
|
|
import "core:fmt"
|
|
import "core:os"
|
|
import "core:path/filepath"
|
|
import "core:strings"
|
|
import "core:testing"
|
|
|
|
import "sqlite"
|
|
|
|
FIXTURES :: "fixtures"
|
|
|
|
test_temp_dir :: proc(t: ^testing.T, prefix: string) -> string {
|
|
dir, err := os.mkdir_temp("", prefix, context.temp_allocator)
|
|
if err != nil {
|
|
testing.fail_now(t, fmt.tprintf("Failed to create temp dir: %v", err))
|
|
}
|
|
return dir
|
|
}
|
|
|
|
fixture_key :: proc() -> SshKeyPair {
|
|
priv, _ := strings.concatenate(
|
|
[]string{FIXTURES, "/keys/insecure-test-key"},
|
|
context.temp_allocator,
|
|
)
|
|
pub, _ := strings.concatenate(
|
|
[]string{FIXTURES, "/keys/insecure-test-key.pub"},
|
|
context.temp_allocator,
|
|
)
|
|
return SshKeyPair{private = priv, public = pub}
|
|
}
|
|
|
|
fixture_db_path :: proc() -> string {
|
|
p, _ := strings.concatenate([]string{FIXTURES, "/single-file.db"}, context.temp_allocator)
|
|
return p
|
|
}
|
|
|
|
fixture_config :: proc() -> Config {
|
|
cfg := Config {
|
|
keys = make([dynamic]SshKeyPair, 0, 1),
|
|
}
|
|
append(&cfg.keys, fixture_key())
|
|
return cfg
|
|
}
|
|
|
|
@(test)
|
|
test_encrypt_decrypt_sqlite_roundtrip :: proc(t: ^testing.T) {
|
|
cfg := fixture_config()
|
|
defer {
|
|
delete(cfg.keys)
|
|
}
|
|
|
|
db_path := fixture_db_path()
|
|
sqlite_data, read_err := os.read_entire_file_from_path(db_path, context.allocator)
|
|
testing.expectf(t, read_err == nil, "failed to read fixture db: %v", read_err)
|
|
if read_err != nil {
|
|
return
|
|
}
|
|
defer delete(sqlite_data)
|
|
|
|
encrypted, enc_ok := encrypt(sqlite_data, cfg.keys[:])
|
|
testing.expect(t, enc_ok, "encryption should succeed")
|
|
if !enc_ok {
|
|
return
|
|
}
|
|
defer delete(encrypted)
|
|
|
|
testing.expect(t, len(encrypted) >= HEADER_SIZE, "ciphertext should have header")
|
|
testing.expect_value(t, encrypted[0], u8('E'))
|
|
testing.expect_value(t, encrypted[1], u8('N'))
|
|
testing.expect_value(t, encrypted[2], u8('V'))
|
|
testing.expect_value(t, encrypted[3], u8('R'))
|
|
|
|
plaintext, dec_ok := decrypt(encrypted, cfg.keys[:])
|
|
testing.expect(t, dec_ok, "decryption should succeed")
|
|
if !dec_ok {
|
|
return
|
|
}
|
|
defer delete(plaintext)
|
|
|
|
testing.expectf(
|
|
t,
|
|
len(plaintext) == len(sqlite_data),
|
|
"round-trip size mismatch: expected %d, got %d",
|
|
len(sqlite_data),
|
|
len(plaintext),
|
|
)
|
|
|
|
match := true
|
|
for i in 0 ..< len(sqlite_data) {
|
|
if plaintext[i] != sqlite_data[i] {
|
|
match = false
|
|
break
|
|
}
|
|
}
|
|
testing.expect(t, match, "decrypted data should match original")
|
|
}
|
|
|
|
@(test)
|
|
test_encrypt_write_read_decrypt :: proc(t: ^testing.T) {
|
|
cfg := fixture_config()
|
|
defer {
|
|
delete(cfg.keys)
|
|
}
|
|
|
|
db_path := fixture_db_path()
|
|
sqlite_data, read_err := os.read_entire_file_from_path(db_path, context.allocator)
|
|
testing.expectf(t, read_err == nil, "failed to read fixture db: %v", read_err)
|
|
if read_err != nil {
|
|
return
|
|
}
|
|
defer delete(sqlite_data)
|
|
|
|
encrypted, enc_ok := encrypt(sqlite_data, cfg.keys[:])
|
|
testing.expect(t, enc_ok, "encryption should succeed")
|
|
if !enc_ok {
|
|
return
|
|
}
|
|
defer delete(encrypted)
|
|
|
|
ewrd_dir := test_temp_dir(t, "envr-test-ewrd-*")
|
|
defer os.remove_all(ewrd_dir)
|
|
tmp_enc_path, _ := filepath.join([]string{ewrd_dir, "data.envr"}, context.temp_allocator)
|
|
write_err := os.write_entire_file(tmp_enc_path, encrypted)
|
|
testing.expectf(t, write_err == nil, "failed to write encrypted file: %v", write_err)
|
|
if write_err != nil {
|
|
return
|
|
}
|
|
|
|
read_back, rb_err := os.read_entire_file_from_path(tmp_enc_path, context.allocator)
|
|
testing.expectf(t, rb_err == nil, "failed to read back encrypted file: %v", rb_err)
|
|
if rb_err != nil {
|
|
return
|
|
}
|
|
defer delete(read_back)
|
|
|
|
plaintext, dec_ok := decrypt(read_back, cfg.keys[:])
|
|
testing.expect(t, dec_ok, "decryption after write/read should succeed")
|
|
if !dec_ok {
|
|
return
|
|
}
|
|
defer delete(plaintext)
|
|
|
|
testing.expect_value(t, len(plaintext), len(sqlite_data))
|
|
}
|
|
|
|
@(test)
|
|
test_decrypt_then_deserialize_sqlite :: proc(t: ^testing.T) {
|
|
cfg := fixture_config()
|
|
defer {
|
|
delete(cfg.keys)
|
|
}
|
|
|
|
db_path := fixture_db_path()
|
|
sqlite_data, read_err := os.read_entire_file_from_path(db_path, context.allocator)
|
|
testing.expectf(t, read_err == nil, "failed to read fixture db: %v", read_err)
|
|
if read_err != nil {
|
|
return
|
|
}
|
|
defer delete(sqlite_data)
|
|
|
|
encrypted, enc_ok := encrypt(sqlite_data, cfg.keys[:])
|
|
testing.expect(t, enc_ok, "encryption should succeed")
|
|
if !enc_ok {
|
|
return
|
|
}
|
|
defer delete(encrypted)
|
|
|
|
plaintext, dec_ok := decrypt(encrypted, cfg.keys[:])
|
|
testing.expect(t, dec_ok, "decryption should succeed")
|
|
if !dec_ok {
|
|
return
|
|
}
|
|
defer delete(plaintext)
|
|
|
|
mem_db: sqlite.Db
|
|
rc := sqlite.open(":memory:", &mem_db)
|
|
testing.expectf(t, rc == sqlite.OK, "failed to open in-memory db")
|
|
if rc != sqlite.OK {
|
|
return
|
|
}
|
|
defer sqlite.close(mem_db)
|
|
|
|
n := i64(len(plaintext))
|
|
buf := sqlite.malloc64(n)
|
|
testing.expect(t, buf != nil, "malloc64 should succeed")
|
|
if buf == nil do return
|
|
copy(buf[:len(plaintext)], plaintext)
|
|
|
|
rc = sqlite.deserialize(mem_db, "main", buf, n, n, {.FREEONCLOSE, .RESIZEABLE})
|
|
testing.expect_value(t, rc, sqlite.OK)
|
|
if rc != sqlite.OK {
|
|
sqlite.free(buf)
|
|
return
|
|
}
|
|
|
|
sql: cstring = "SELECT path FROM envr_env_files"
|
|
stmt: sqlite.Stmt
|
|
rc = sqlite.prepare_v2(mem_db, sql, -1, &stmt, nil)
|
|
testing.expect_value(t, rc, sqlite.OK)
|
|
if rc != sqlite.OK {
|
|
return
|
|
}
|
|
defer sqlite.finalize(stmt)
|
|
|
|
rc = sqlite.step(stmt)
|
|
testing.expect_value(t, rc, sqlite.ROW)
|
|
if rc == sqlite.ROW {
|
|
path := string(sqlite.column_text(stmt, 0))
|
|
testing.expect(t, len(path) > 0, "path should not be empty")
|
|
}
|
|
}
|
|
|
|
@(test)
|
|
test_full_db_cycle :: proc(t: ^testing.T) {
|
|
cfg := fixture_config()
|
|
defer delete(cfg.keys)
|
|
|
|
db_path := fixture_db_path()
|
|
original_data, read_err := os.read_entire_file_from_path(db_path, context.allocator)
|
|
testing.expectf(t, read_err == nil, "failed to read fixture db: %v", read_err)
|
|
if read_err != nil {
|
|
return
|
|
}
|
|
defer delete(original_data)
|
|
|
|
encrypted, enc_ok := encrypt(original_data, cfg.keys[:])
|
|
testing.expect(t, enc_ok, "first encryption should succeed")
|
|
if !enc_ok {
|
|
return
|
|
}
|
|
defer delete(encrypted)
|
|
|
|
cycle_dir := test_temp_dir(t, "envr-test-cycle-*")
|
|
defer os.remove_all(cycle_dir)
|
|
envr_dir_path, _ := filepath.join([]string{cycle_dir, ".envr"}, context.temp_allocator)
|
|
{
|
|
err := os.mkdir_all(envr_dir_path)
|
|
testing.expect_value(t, err, nil)
|
|
}
|
|
|
|
data_path, _ := filepath.join([]string{envr_dir_path, "data.envr"}, context.temp_allocator)
|
|
write_err := os.write_entire_file(data_path, encrypted)
|
|
testing.expectf(t, write_err == nil, "failed to write data.envr: %v", write_err)
|
|
if write_err != nil {
|
|
return
|
|
}
|
|
|
|
read_back, rb_err := os.read_entire_file_from_path(data_path, context.allocator)
|
|
testing.expectf(t, rb_err == nil, "failed to read data.envr: %v", rb_err)
|
|
if rb_err != nil {
|
|
return
|
|
}
|
|
defer delete(read_back)
|
|
|
|
plaintext, dec_ok := decrypt(read_back, cfg.keys[:])
|
|
testing.expect(t, dec_ok, "decryption should succeed")
|
|
if !dec_ok {
|
|
return
|
|
}
|
|
defer delete(plaintext)
|
|
|
|
encrypted2, enc2_ok := encrypt(plaintext, cfg.keys[:])
|
|
testing.expect(t, enc2_ok, "re-encryption should succeed")
|
|
if !enc2_ok {
|
|
return
|
|
}
|
|
defer delete(encrypted2)
|
|
|
|
plaintext2, dec2_ok := decrypt(encrypted2, cfg.keys[:])
|
|
testing.expect(t, dec2_ok, "second decryption should succeed")
|
|
if !dec2_ok {
|
|
return
|
|
}
|
|
defer delete(plaintext2)
|
|
|
|
testing.expect_value(t, len(plaintext2), len(original_data))
|
|
|
|
os.remove(data_path)
|
|
os.remove(envr_dir_path)
|
|
home := filepath.dir(filepath.dir(envr_dir_path))
|
|
os.remove(home)
|
|
}
|
|
|
|
@(test)
|
|
test_ssh_key_parse_from_fixtures :: proc(t: ^testing.T) {
|
|
key := fixture_key()
|
|
|
|
priv_kp, priv_ok := parse_ssh_private_key(key.private)
|
|
testing.expect(t, priv_ok, "should parse private key from fixtures")
|
|
if !priv_ok {
|
|
return
|
|
}
|
|
|
|
pub_key, pub_ok := parse_ssh_public_key(key.public)
|
|
testing.expect(t, pub_ok, "should parse public key from fixtures")
|
|
if !pub_ok {
|
|
return
|
|
}
|
|
|
|
for i in 0 ..< 32 {
|
|
testing.expectf(t, priv_kp.Public[i] == pub_key[i], "public key mismatch at byte %d", i)
|
|
}
|
|
|
|
x25519_pairs, x_ok := ssh_to_x25519([]SshKeyPair{key})
|
|
testing.expect(t, x_ok, "ssh_to_x25519 should succeed")
|
|
if !x_ok {
|
|
return
|
|
}
|
|
|
|
testing.expect_value(t, len(x25519_pairs), 1)
|
|
}
|
|
|
|
@(test)
|
|
test_config_load_with_fixture_key :: proc(t: ^testing.T) {
|
|
cfg := fixture_config()
|
|
defer {
|
|
delete(cfg.keys)
|
|
}
|
|
|
|
testing.expect_value(t, len(cfg.keys), 1)
|
|
|
|
key := cfg.keys[0]
|
|
|
|
testing.expectf(t, len(key.private) > 0, "private key path should not be empty")
|
|
testing.expectf(t, len(key.public) > 0, "public key path should not be empty")
|
|
|
|
_, priv_ok := parse_ssh_private_key(key.private)
|
|
testing.expect(t, priv_ok, "should parse private key using config paths")
|
|
if !priv_ok {
|
|
fmt.printf(" private key path was: '%s'\n", key.private)
|
|
}
|
|
}
|
|
|