changed how tests are ran; updated dotenv to handle parsing fields into structs
CI / Detect changed paths (pull_request) Failing after 31m29s
CI / Infra unit tests, vet, and preview (pull_request) Has been skipped
CI / API unit tests and lint (pull_request) Failing after 34m25s
CI / Odin unit tests and build (pull_request) Failing after 34m36s

This commit is contained in:
2026-09-07 14:35:30 -06:00
parent ac2d30510c
commit ba24005bfb
6 changed files with 386 additions and 187 deletions
+201
View File
@@ -0,0 +1,201 @@
package dotenv_tests
import "core:os"
import "core:strings"
import "core:testing"
import dotenv "lib:dotenv/src"
Test_Config :: struct {
api_url: string,
debug: bool,
port: int,
ratio: f64,
}
// load_env writes src to a unique temp file and parses it via parse_file.
// The returned map owns its strings; callers must destroy it.
load_env :: proc(t: ^testing.T, src: string) -> map[string]string {
dir, err := os.make_directory_temp("", "dotenv_test_*", context.allocator)
testing.expect(t, err == nil, "expected temp dir to be created")
defer os.remove_all(dir)
defer delete(dir)
path := strings.concatenate({dir, "/.env"})
defer delete(path)
testing.expect(
t,
os.write_entire_file(path, src) == nil,
"expected file write to succeed",
)
env, ok := dotenv.parse_file(path)
testing.expect(t, ok, "expected parse_file to succeed")
return env
}
@(test)
test_parse_basic :: proc(t: ^testing.T) {
env := load_env(t, "API_URL=http://127.0.0.1:8080\nDEBUG=true\nPORT=8080\n")
defer dotenv.destroy(env)
testing.expect(t, env["API_URL"] == "http://127.0.0.1:8080")
testing.expect(t, env["DEBUG"] == "true")
testing.expect(t, env["PORT"] == "8080")
}
@(test)
test_parse_ignores_comments_and_blank_lines :: proc(t: ^testing.T) {
env := load_env(t, "# leading comment\n\n \nFOO=bar \nBAZ = qux \n")
defer dotenv.destroy(env)
testing.expect(t, env["FOO"] == "bar", "value should be trimmed")
testing.expect(
t,
env["BAZ"] == "qux",
"key and value should be trimmed around '='",
)
testing.expect(
t,
"API_URL" not_in env,
"comment-only lines should not be parsed",
)
}
@(test)
test_parse_quoted_values :: proc(t: ^testing.T) {
env := load_env(t, "GREETING=\"hello world\"\nEMPTY=\"\"\n")
defer dotenv.destroy(env)
testing.expect(
t,
env["GREETING"] == "hello world",
"quoted value with inner space",
)
testing.expect(t, env["EMPTY"] == "", "double-quoted empty value")
}
@(test)
test_parse_skips_lines_without_equals :: proc(t: ^testing.T) {
env := load_env(t, "not-an-assignment\nOK=yep\n")
defer dotenv.destroy(env)
testing.expect(t, env["OK"] == "yep")
testing.expect(
t,
"not-an-assignment" not_in env,
"line without '=' should be skipped",
)
}
@(test)
test_parse_missing_file :: proc(t: ^testing.T) {
env, ok := dotenv.parse_file("/nonexistent/dotenv_test_does_not_exist.env")
testing.expect(t, !ok, "missing file should report failure")
testing.expect(t, env == nil, "missing file should return nil map")
}
@(test)
test_real_env_overrides_file :: proc(t: ^testing.T) {
testing.expect(t, os.set_env("DESI_EXPLORER_TEST_FOO", "from_env") == nil)
defer os.unset_env("DESI_EXPLORER_TEST_FOO")
env := load_env(t, "DESI_EXPLORER_TEST_FOO=from_file\n")
defer dotenv.destroy(env)
testing.expect(
t,
env["DESI_EXPLORER_TEST_FOO"] == "from_env",
"real env var should win over file",
)
}
@(test)
test_decode_maps_fields_case_insensitively :: proc(t: ^testing.T) {
env := load_env(
t,
"API_URL=http://127.0.0.1:8080\nDEBUG=true\nPORT=8080\nRATIO=0.5\n",
)
defer dotenv.destroy(env)
cfg := Test_Config{}
testing.expect(t, dotenv.decode(env, &cfg))
defer delete(cfg.api_url)
testing.expect(
t,
cfg.api_url == "http://127.0.0.1:8080",
"API_URL maps onto api_url",
)
testing.expect(t, cfg.debug == true, "DEBUG=true should decode to true")
testing.expect(t, cfg.port == 8080, "PORT=8080 should decode to int 8080")
testing.expect(t, cfg.ratio == 0.5, "RATIO=0.5 should decode to f64 0.5")
}
@(test)
test_decode_matches_exact_and_lowercase_keys :: proc(t: ^testing.T) {
env := load_env(t, "api_url=http://exact\nPort=9090\n")
defer dotenv.destroy(env)
cfg := Test_Config{}
testing.expect(t, dotenv.decode(env, &cfg))
defer delete(cfg.api_url)
testing.expect(
t,
cfg.api_url == "http://exact",
"exact-case key should match",
)
testing.expect(t, cfg.port == 9090, "mixed-case key should match field")
}
@(test)
test_decode_missing_keys_leave_zero_values :: proc(t: ^testing.T) {
env := load_env(t, "UNRELATED=value\n")
defer dotenv.destroy(env)
cfg := Test_Config{}
testing.expect(t, dotenv.decode(env, &cfg))
testing.expect(t, cfg.api_url == "")
testing.expect(t, !cfg.debug)
testing.expect(t, cfg.port == 0)
testing.expect(t, cfg.ratio == 0)
}
@(test)
test_decode_unparsable_int_fails :: proc(t: ^testing.T) {
env := load_env(t, "PORT=oops\n")
defer dotenv.destroy(env)
cfg := Test_Config{}
testing.expect(
t,
!dotenv.decode(env, &cfg),
"unparsable int should make decode fail",
)
}
@(test)
test_decode_unparsable_bool_fails :: proc(t: ^testing.T) {
env := load_env(t, "DEBUG=maybe\nAPI_URL=http://127.0.0.1:8080\n")
defer dotenv.destroy(env)
cfg := Test_Config{}
testing.expect(
t,
!dotenv.decode(env, &cfg),
"unparsable bool should make decode fail",
)
defer delete(cfg.api_url)
}
@(test)
test_decode_hex_and_negative_ints :: proc(t: ^testing.T) {
env := load_env(t, "PORT=0x1F\n")
defer dotenv.destroy(env)
cfg := Test_Config{}
testing.expect(t, dotenv.decode(env, &cfg))
testing.expect(t, cfg.port == 31, "hex int should decode")
}