deno.land / std@0.224.0 / dotenv / mod_test.ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { assert, assertEquals, assertRejects, assertStrictEquals, assertThrows,} from "../assert/mod.ts";import { load, type LoadOptions, loadSync, MissingEnvVarsError,} from "./mod.ts";import * as path from "../path/mod.ts";
const moduleDir = path.dirname(path.fromFileUrl(import.meta.url));const testdataDir = path.resolve(moduleDir, "testdata");
const testOptions = Object.freeze({ envPath: path.join(testdataDir, ".env"), defaultsPath: path.join(testdataDir, ".env.defaults"),});
Deno.test("load() handles non-existent .env files", async () => { //n.b. neither .env nor .env.default exist in the current directory assertEquals({}, await load()); assertEquals({}, loadSync());
const loadOptions = { envPath: "some.nonexistent.env", examplePath: "some.nonexistent.example", defaultsPath: "some.nonexistent.defaults", }; assertEquals({}, await load(loadOptions)); assertEquals({}, loadSync(loadOptions));});
Deno.test("load() handles build from .env.default only", async () => { const conf = loadSync({ defaultsPath: path.join(testdataDir, ".env.defaults"), }); assertEquals(conf.DEFAULT1, "Some Default", "loaded from .env.default");
const asyncConf = await load({ defaultsPath: path.join(testdataDir, ".env.defaults"), }); assertEquals(asyncConf.DEFAULT1, "Some Default", "loaded from .env.default");});
Deno.test("load() handles comprised .env and .env.defaults", async () => { const conf = loadSync(testOptions); assertEquals(conf.GREETING, "hello world", "loaded from .env"); assertEquals(conf.DEFAULT1, "Some Default", "loaded from .env.default");
const asyncConf = await load(testOptions); assertEquals(asyncConf.GREETING, "hello world", "loaded from .env"); assertEquals(asyncConf.DEFAULT1, "Some Default", "loaded from .env.default");});
Deno.test("load() handles exported entries accessibility in Deno.env", async () => { assert(Deno.env.get("GREETING") === undefined, "GREETING is not set"); assert(Deno.env.get("DEFAULT1") === undefined, "DEFAULT1 is not set");
loadSync({ ...testOptions, export: true }); validateExport();
await load({ ...testOptions, export: true }); validateExport();});
function validateExport(): void { try { assertEquals( Deno.env.get("GREETING"), "hello world", "exported from .env -> Deno.env", ); assertEquals( Deno.env.get("DEFAULT1"), "Some Default", "exported from .env.default -> Deno.env", ); } finally { Deno.env.delete("GREETING"); Deno.env.delete("DEFAULT1"); }}
Deno.test("load() process does not overridde env vars by .env values", async () => { Deno.env.set("GREETING", "Do not override!"); assert(Deno.env.get("DEFAULT1") === undefined, "DEFAULT1 is not set");
validateNotOverridden(loadSync({ ...testOptions, export: true })); validateNotOverridden(await load({ ...testOptions, export: true }));});
function validateNotOverridden(conf: Record<string, string>): void { try { assertEquals(conf.GREETING, "hello world", "value from .env"); assertEquals( Deno.env.get("GREETING"), "Do not override!", "not exported from .env -> Deno.env", ); assertEquals( Deno.env.get("DEFAULT1"), "Some Default", "exported from .env.default -> Deno.env", ); } finally { Deno.env.delete("DEFAULT1"); }}
Deno.test("load() handles example file key in .env, no issues loading", async () => { //Both .env.example.test and .env contain "GREETING" const loadOptions = { ...testOptions, examplePath: path.join(testdataDir, "./.env.example.test"), }; loadSync(loadOptions); await load(loadOptions);});
Deno.test("load() handles example file key in .env.default, no issues loading", async () => { //Both .env.example3.test and .env.default contain "DEFAULT1" const loadOptions = { ...testOptions, examplePath: path.join(testdataDir, "./.env.example3.test"), }; loadSync(loadOptions); await load(loadOptions);});
Deno.test("load() handles example file key not in .env or .env.defaults, error thrown", async () => { // Example file key of "ANOTHER" is not present in .env or .env.defaults const error: MissingEnvVarsError = assertThrows(() => { loadSync({ ...testOptions, examplePath: path.join(testdataDir, "./.env.example2.test"), }); }, MissingEnvVarsError);
assertEquals(error.missing, ["ANOTHER"]);
const asyncError: MissingEnvVarsError = await assertRejects(async () => { await load({ ...testOptions, examplePath: path.join(testdataDir, "./.env.example2.test"), }); }, MissingEnvVarsError);
assertEquals(asyncError.missing, ["ANOTHER"]);});
Deno.test("load() handles omitted allowEmptyValues, empty required keys throw error", async () => { // Example file key of "ANOTHER" is present but empty in .env const error: MissingEnvVarsError = assertThrows(() => { loadSync({ envPath: path.join(testdataDir, "./.env.required.empty.test"), examplePath: path.join(testdataDir, "./.env.example2.test"), }); }, MissingEnvVarsError);
assertEquals(error.missing, ["ANOTHER"]);
const asyncError: MissingEnvVarsError = await assertRejects(async () => { await load({ envPath: path.join(testdataDir, "./.env.required.empty.test"), examplePath: path.join(testdataDir, "./.env.example2.test"), }); }, MissingEnvVarsError);
assertEquals(asyncError.missing, ["ANOTHER"]);});
Deno.test("load() handles allowEmptyValues, empty required keys do not throw error", async () => { // Example file key of "ANOTHER" is present but empty in .env const loadOptions = { envPath: path.join(testdataDir, "./.env.required.empty.test"), examplePath: path.join(testdataDir, "./.env.example2.test"), allowEmptyValues: true, };
loadSync(loadOptions); await load(loadOptions);});
Deno.test("load() checks that required keys can be sourced from process environment", async () => { try { Deno.env.set("ANOTHER", "VAR");
// Example file key of "ANOTHER" is not present in .env or .env.defaults const loadOptions = { envPath: path.join(testdataDir, "./.env"), examplePath: path.join(testdataDir, "./.env.example2.test"), };
loadSync(loadOptions); await load(loadOptions); } finally { Deno.env.delete("ANOTHER"); }});
Deno.test("load() checks that required keys sourced from process environment cannot be empty", async () => { try { Deno.env.set("ANOTHER", "");
// Example file key of "ANOTHER" is not present in .env or .env.defaults const loadOptions = { envPath: path.join(testdataDir, "./.env"), examplePath: path.join(testdataDir, "./.env.example2.test"), };
const error: MissingEnvVarsError = assertThrows(() => { loadSync(loadOptions); }, MissingEnvVarsError);
assertEquals(error.missing, ["ANOTHER"]);
const asyncError: MissingEnvVarsError = await assertRejects(async () => { await load(loadOptions); }, MissingEnvVarsError);
assertEquals(asyncError.missing, ["ANOTHER"]); } finally { Deno.env.delete("ANOTHER"); }});
Deno.test("load() checks that required keys sourced from process environment can be empty with allowEmptyValues", async () => { try { Deno.env.set("ANOTHER", "");
// Example file key of "ANOTHER" is not present in .env or .env.defaults const loadOptions = { envPath: path.join(testdataDir, "./.env"), examplePath: path.join(testdataDir, "./.env.example2.test"), allowEmptyValues: true, };
loadSync(loadOptions); await load(loadOptions); } finally { Deno.env.delete("ANOTHER"); }});
Deno.test("load() loads .env and .env.defaults successfully from default file names/paths", async () => { const command = new Deno.Command(Deno.execPath(), { args: [ "run", "--no-lock", "--allow-read", "--allow-env", path.join(testdataDir, "./app_defaults.ts"), ], cwd: testdataDir, }); const { stdout } = await command.output();
const decoder = new TextDecoder(); const conf = JSON.parse(decoder.decode(stdout).trim());
assertEquals(conf.GREETING, "hello world", "fetches .env by default"); assertEquals(conf.DEFAULT1, "Some Default", "default value loaded");});
Deno.test("load() expands empty values from process env expand as empty value", async () => { try { Deno.env.set("EMPTY", "");
// .env.single.expand contains one key which expands to the "EMPTY" process env var const loadOptions = { envPath: path.join(testdataDir, "./.env.single.expand"), allowEmptyValues: true, };
const conf = loadSync(loadOptions); assertEquals( conf.EXPECT_EMPTY, "", "empty value expanded from process env", );
const asyncConf = await load(loadOptions); assertEquals( asyncConf.EXPECT_EMPTY, "", "empty value expanded from process env", ); } finally { Deno.env.delete("EMPTY"); }});
Deno.test( "loadSync() checks that --allow-env is not required if no process env vars are expanded upon", { permissions: { read: true, }, }, () => { // note lack of --allow-env permission const conf = loadSync(testOptions); assertEquals(conf.GREETING, "hello world"); assertEquals(conf.DEFAULT1, "Some Default"); },);
Deno.test( "loadSync() checks that --allow-env is required when process env vars are expanded upon", { permissions: { read: true, }, }, () => { // ./app_permission_test.ts loads a .env with one key which expands a process env var // note lack of --allow-env permission const loadOptions = { envPath: path.join(testdataDir, "./.env.single.expand"), defaultsPath: null, examplePath: null, }; assertThrows( () => loadSync(loadOptions), Deno.errors.PermissionDenied, `Requires env access to "EMPTY", run again with the --allow-env flag`, ); },);
Deno.test( "loadSync() checks that --allow-env restricted access works when process env vars are expanded upon", { permissions: { read: true, env: ["EMPTY"], }, }, () => { try { Deno.env.set("EMPTY", "");
const loadOptions = { envPath: path.join(testdataDir, "./.env.single.expand"), defaultsPath: null, examplePath: null, }; const conf = loadSync(loadOptions); assertEquals( conf.EXPECT_EMPTY, "", "empty value expanded from process env", ); } finally { Deno.env.delete("EMPTY"); } },);
//TODO test permissions
Deno.test( "loadSync() prevents file system reads of default path parameter values by using explicit null", { permissions: { env: ["GREETING", "DO_NOT_OVERRIDE"], read: [path.join(testdataDir, "./.env.multiple")], }, }, async (t) => { const optsNoPaths = { defaultsPath: null, envPath: null, examplePath: null, } satisfies LoadOptions;
const optsEnvPath = { envPath: path.join(testdataDir, "./.env.multiple"), } satisfies LoadOptions;
const optsOnlyEnvPath = { ...optsEnvPath, defaultsPath: null, examplePath: null, } satisfies LoadOptions;
const assertEnv = (env: Record<string, string>): void => { assertStrictEquals(Object.keys(env).length, 2); assertStrictEquals(env["GREETING"], "hello world"); assertStrictEquals(env["DO_NOT_OVERRIDE"], "overridden"); };
await t.step("load", async () => { assertStrictEquals(Object.keys(await load(optsNoPaths)).length, 0); assertEnv(await load(optsOnlyEnvPath));
await assertRejects( () => load(optsEnvPath), Deno.errors.PermissionDenied, `Requires read access to ".env.defaults"`, );
await assertRejects( () => load({ ...optsEnvPath, defaultsPath: null }), Deno.errors.PermissionDenied, `Requires read access to ".env.example"`, );
await assertRejects( () => load({ ...optsEnvPath, examplePath: null }), Deno.errors.PermissionDenied, `Requires read access to ".env.defaults"`, ); });
await t.step("loadSync", () => { assertStrictEquals(Object.keys(loadSync(optsNoPaths)).length, 0); assertEnv(loadSync(optsOnlyEnvPath));
assertThrows( () => loadSync(optsEnvPath), Deno.errors.PermissionDenied, `Requires read access to ".env.defaults"`, );
assertThrows( () => loadSync({ ...optsEnvPath, defaultsPath: null }), Deno.errors.PermissionDenied, `Requires read access to ".env.example"`, );
assertThrows( () => loadSync({ ...optsEnvPath, examplePath: null }), Deno.errors.PermissionDenied, `Requires read access to ".env.defaults"`, ); }); },);
Version Info