From 2039c4edc6cc0b29409004e447feca61abf424ac Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Wed, 24 Jan 2024 09:31:22 -0800 Subject: [PATCH 1/3] wip: fix tests --- tests/test-config.js | 158 +++++++++++++++++++------------------------ 1 file changed, 70 insertions(+), 88 deletions(-) diff --git a/tests/test-config.js b/tests/test-config.js index ea771ad8..da57c8e8 100644 --- a/tests/test-config.js +++ b/tests/test-config.js @@ -1,6 +1,6 @@ const fs = require('fs') -const os = require('os') -const path = require('path') +// const os = require('os') +// const path = require('path') const sinon = require('sinon') const t = require('tap') @@ -8,75 +8,58 @@ const t = require('tap') const dotenv = require('../lib/main') const mockParseResponse = { test: 'foo' } -let readFileSyncStub -let parseStub - -t.beforeEach(() => { - readFileSyncStub = sinon.stub(fs, 'readFileSync').returns('test=foo') - parseStub = sinon.stub(dotenv, 'parse').returns(mockParseResponse) -}) - -t.afterEach(() => { - readFileSyncStub.restore() - parseStub.restore() -}) - -t.test('takes string for path option', ct => { - ct.plan(1) - - const testPath = 'tests/.env' - dotenv.config({ path: testPath }) - - ct.equal(readFileSyncStub.args[0][0], testPath) -}) t.test('takes string for path option', ct => { - ct.plan(1) + ct.plan(2) const testPath = 'tests/.env' - dotenv.config({ path: testPath }) + const env = dotenv.config({ path: testPath }) - ct.equal(readFileSyncStub.args[0][0], testPath) + ct.equal(env.parsed.BASIC, 'basic') + ct.equal(process.env.BASIC, 'basic') }) t.test('takes array for path option', ct => { - ct.plan(1) + ct.plan(2) const testPath = ['tests/.env'] - dotenv.config({ path: testPath }) + const env = dotenv.config({ path: testPath }) - ct.equal(readFileSyncStub.args[0][0], testPath) + ct.equal(env.parsed.BASIC, 'basic') + ct.equal(process.env.BASIC, 'basic') }) t.test('takes two or more files in the array for path option', ct => { - ct.plan(1) + ct.plan(2) const testPath = ['tests/.env.local', 'tests/.env'] - dotenv.config({ path: testPath }) + const env = dotenv.config({ path: testPath }) - ct.equal(readFileSyncStub.args[0][0], testPath) + ct.equal(env.parsed.BASIC, 'local_basic') + ct.equal(process.env.BASIC, 'local_basic') }) t.test('takes URL for path option', ct => { - ct.plan(1) + ct.plan(2) const testPath = new URL('file://home/user/project/.env') - dotenv.config({ path: testPath }) + const env = dotenv.config({ path: testPath }) - ct.equal(readFileSyncStub.args[0][0], testPath) + ct.equal(env.parsed.BASIC, 'basic') + ct.equal(process.env.BASIC, 'basic') }) -t.test('takes option for path along with home directory char ~', ct => { - ct.plan(2) - const mockedHomedir = '/Users/dummy' - const homedirStub = sinon.stub(os, 'homedir').returns(mockedHomedir) - const testPath = '~/.env' - dotenv.config({ path: testPath }) - - ct.equal(readFileSyncStub.args[0][0], path.join(mockedHomedir, '.env')) - ct.ok(homedirStub.called) - homedirStub.restore() -}) +// t.test('takes option for path along with home directory char ~', ct => { +// ct.plan(2) +// const mockedHomedir = '/Users/dummy' +// const homedirStub = sinon.stub(os, 'homedir').returns(mockedHomedir) +// const testPath = '~/.env' +// dotenv.config({ path: testPath }) +// +// ct.equal(readFileSyncStub.args[0][0], path.join(mockedHomedir, '.env')) +// ct.ok(homedirStub.called) +// homedirStub.restore() +// }) t.test('takes option for encoding', ct => { ct.plan(1) @@ -84,7 +67,10 @@ t.test('takes option for encoding', ct => { const testEncoding = 'latin1' dotenv.config({ encoding: testEncoding }) + const readFileSyncStub = sinon.stub(fs, 'readFileSync').returns('test=foo') + ct.equal(readFileSyncStub.args[0][1].encoding, testEncoding) + readFileSyncStub.restore() }) t.test('takes option for debug', ct => { @@ -100,80 +86,70 @@ t.test('takes option for debug', ct => { t.test('reads path with encoding, parsing output to process.env', ct => { ct.plan(2) - const res = dotenv.config() + const env = dotenv.config() - ct.same(res.parsed, mockParseResponse) - ct.equal(readFileSyncStub.callCount, 1) + ct.same(env.parsed.BASIC, 'basic') + ct.same(process.env.BASIC, 'basic') }) t.test('does not write over keys already in process.env', ct => { ct.plan(2) const existing = 'bar' - process.env.test = existing - // 'foo' returned as value in `beforeEach`. should keep this 'bar' + process.env.BASIC = existing const env = dotenv.config() - ct.equal(env.parsed && env.parsed.test, mockParseResponse.test) - ct.equal(process.env.test, existing) + ct.equal(env.parsed.BASIC, 'basic') + ct.equal(process.env.BASIC, existing) }) t.test('does write over keys already in process.env if override turned on', ct => { ct.plan(2) const existing = 'bar' - process.env.test = existing - // 'foo' returned as value in `beforeEach`. should keep this 'bar' + process.env.BASIC = existing const env = dotenv.config({ override: true }) - ct.equal(env.parsed && env.parsed.test, mockParseResponse.test) - ct.equal(process.env.test, 'foo') + ct.equal(env.parsed.BASIC, 'basic') + ct.equal(process.env.BASIC, 'basic') }) -t.test( - 'does not write over keys already in process.env if the key has a falsy value', - ct => { - ct.plan(2) +t.test('does not write over keys already in process.env if the key has a falsy value', ct => { + ct.plan(2) - const existing = '' - process.env.test = existing - // 'foo' returned as value in `beforeEach`. should keep this '' - const env = dotenv.config() + const existing = '' + process.env.BASIC = existing + const env = dotenv.config() - ct.equal(env.parsed && env.parsed.test, mockParseResponse.test) - // NB: process.env.test becomes undefined on Windows - ct.notOk(process.env.test) - } -) + ct.equal(env.parsed.BASIC, 'basic') + ct.equal(process.env.BASIC, undefined) +}) -t.test( - 'does write over keys already in process.env if the key has a falsy value but override is set to true', - ct => { - ct.plan(2) +t.test('does write over keys already in process.env if the key has a falsy value but override is set to true', ct => { + ct.plan(2) - const existing = '' - process.env.test = existing - // 'foo' returned as value in `beforeEach`. should keep this '' - const env = dotenv.config({ override: true }) + const existing = '' + process.env.BASIC = existing + // 'foo' returned as value in `beforeEach`. should keep this '' + const env = dotenv.config({ override: true }) - ct.equal(env.parsed && env.parsed.test, mockParseResponse.test) - // NB: process.env.test becomes undefined on Windows - ct.ok(process.env.test) - } -) + ct.equal(env.parsed.BASIC, 'basic') + ct.equal(process.env.BASIC, '') + // ct.ok(process.env.test) +}) t.test('can write to a different object rather than process.env', ct => { ct.plan(3) - process.env.test = 'other' // reset process.env + process.env.BASIC = 'other' // reset process.env const myObject = {} const env = dotenv.config({ processEnv: myObject }) - ct.equal(env.parsed && env.parsed.test, mockParseResponse.test) - console.log('logging', process.env.test) - ct.equal(process.env.test, 'other') - ct.equal(myObject.test, mockParseResponse.test) + ct.equal(env.parsed.BASIC, 'basic') + console.log('logging', process.env.BASIC) + ct.equal(process.env.BASIC, 'other') + ct.equal(myObject.BASIC, 'basic') }) t.test('returns parsed object', ct => { @@ -188,16 +164,21 @@ t.test('returns parsed object', ct => { t.test('returns any errors thrown from reading file or parsing', ct => { ct.plan(1) + const readFileSyncStub = sinon.stub(fs, 'readFileSync').returns('test=foo') + readFileSyncStub.throws() const env = dotenv.config() ct.type(env.error, Error) + + readFileSyncStub.restore() }) t.test('logs any errors thrown from reading file or parsing when in debug mode', ct => { ct.plan(2) const logStub = sinon.stub(console, 'log') + const readFileSyncStub = sinon.stub(fs, 'readFileSync').returns('test=foo') readFileSyncStub.throws() const env = dotenv.config({ debug: true }) @@ -206,6 +187,7 @@ t.test('logs any errors thrown from reading file or parsing when in debug mode', ct.type(env.error, Error) logStub.restore() + readFileSyncStub.restore() }) t.test('logs any errors parsing when in debug and override mode', ct => { From 3e2284b89d31648d89c63f696de9c7e6d5438518 Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Wed, 24 Jan 2024 10:28:46 -0800 Subject: [PATCH 2/3] largely remove mocking from tests except where useful --- tests/test-config.js | 133 ++++++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 59 deletions(-) diff --git a/tests/test-config.js b/tests/test-config.js index da57c8e8..1a24c6bc 100644 --- a/tests/test-config.js +++ b/tests/test-config.js @@ -1,169 +1,182 @@ const fs = require('fs') -// const os = require('os') -// const path = require('path') +const os = require('os') +const path = require('path') const sinon = require('sinon') const t = require('tap') const dotenv = require('../lib/main') -const mockParseResponse = { test: 'foo' } +t.beforeEach(() => { + delete process.env.BASIC // reset +}) t.test('takes string for path option', ct => { - ct.plan(2) - const testPath = 'tests/.env' const env = dotenv.config({ path: testPath }) ct.equal(env.parsed.BASIC, 'basic') ct.equal(process.env.BASIC, 'basic') + + ct.end() }) t.test('takes array for path option', ct => { - ct.plan(2) - const testPath = ['tests/.env'] const env = dotenv.config({ path: testPath }) ct.equal(env.parsed.BASIC, 'basic') ct.equal(process.env.BASIC, 'basic') + + ct.end() }) t.test('takes two or more files in the array for path option', ct => { - ct.plan(2) - const testPath = ['tests/.env.local', 'tests/.env'] const env = dotenv.config({ path: testPath }) ct.equal(env.parsed.BASIC, 'local_basic') ct.equal(process.env.BASIC, 'local_basic') + + ct.end() }) t.test('takes URL for path option', ct => { - ct.plan(2) + const envPath = path.resolve(__dirname, '.env') + const fileUrl = new URL(`file://${envPath}`) - const testPath = new URL('file://home/user/project/.env') - const env = dotenv.config({ path: testPath }) + const env = dotenv.config({ path: fileUrl }) ct.equal(env.parsed.BASIC, 'basic') ct.equal(process.env.BASIC, 'basic') + + ct.end() }) -// t.test('takes option for path along with home directory char ~', ct => { -// ct.plan(2) -// const mockedHomedir = '/Users/dummy' -// const homedirStub = sinon.stub(os, 'homedir').returns(mockedHomedir) -// const testPath = '~/.env' -// dotenv.config({ path: testPath }) -// -// ct.equal(readFileSyncStub.args[0][0], path.join(mockedHomedir, '.env')) -// ct.ok(homedirStub.called) -// homedirStub.restore() -// }) +t.test('takes option for path along with home directory char ~', ct => { + const readFileSyncStub = sinon.stub(fs, 'readFileSync').returns('test=foo') + const mockedHomedir = '/Users/dummy' + const homedirStub = sinon.stub(os, 'homedir').returns(mockedHomedir) + const testPath = '~/.env' + dotenv.config({ path: testPath }) -t.test('takes option for encoding', ct => { - ct.plan(1) + ct.equal(readFileSyncStub.args[0][0], path.join(mockedHomedir, '.env')) + ct.ok(homedirStub.called) - const testEncoding = 'latin1' - dotenv.config({ encoding: testEncoding }) + homedirStub.restore() + readFileSyncStub.restore() + ct.end() +}) +t.test('takes option for encoding', ct => { const readFileSyncStub = sinon.stub(fs, 'readFileSync').returns('test=foo') + const testEncoding = 'latin1' + dotenv.config({ encoding: testEncoding }) ct.equal(readFileSyncStub.args[0][1].encoding, testEncoding) + readFileSyncStub.restore() + ct.end() }) t.test('takes option for debug', ct => { - ct.plan(1) - const logStub = sinon.stub(console, 'log') - dotenv.config({ debug: 'true' }) + dotenv.config({ debug: 'true' }) ct.ok(logStub.called) + logStub.restore() + ct.end() }) t.test('reads path with encoding, parsing output to process.env', ct => { - ct.plan(2) + const readFileSyncStub = sinon.stub(fs, 'readFileSync').returns('BASIC=basic') + const parseStub = sinon.stub(dotenv, 'parse').returns({ BASIC: 'basic' }) - const env = dotenv.config() + const res = dotenv.config() - ct.same(env.parsed.BASIC, 'basic') - ct.same(process.env.BASIC, 'basic') + ct.same(res.parsed, { BASIC: 'basic' }) + ct.equal(readFileSyncStub.callCount, 1) + + readFileSyncStub.restore() + parseStub.restore() + + ct.end() }) t.test('does not write over keys already in process.env', ct => { - ct.plan(2) - + const testPath = 'tests/.env' const existing = 'bar' process.env.BASIC = existing - const env = dotenv.config() + const env = dotenv.config({ path: testPath }) ct.equal(env.parsed.BASIC, 'basic') ct.equal(process.env.BASIC, existing) + + ct.end() }) t.test('does write over keys already in process.env if override turned on', ct => { - ct.plan(2) - + const testPath = 'tests/.env' const existing = 'bar' process.env.BASIC = existing - const env = dotenv.config({ override: true }) + const env = dotenv.config({ path: testPath, override: true }) ct.equal(env.parsed.BASIC, 'basic') ct.equal(process.env.BASIC, 'basic') + + ct.end() }) t.test('does not write over keys already in process.env if the key has a falsy value', ct => { - ct.plan(2) - + const testPath = 'tests/.env' const existing = '' process.env.BASIC = existing - const env = dotenv.config() + const env = dotenv.config({ path: testPath }) ct.equal(env.parsed.BASIC, 'basic') - ct.equal(process.env.BASIC, undefined) + ct.equal(process.env.BASIC, '') + + ct.end() }) t.test('does write over keys already in process.env if the key has a falsy value but override is set to true', ct => { - ct.plan(2) - + const testPath = 'tests/.env' const existing = '' process.env.BASIC = existing - // 'foo' returned as value in `beforeEach`. should keep this '' - const env = dotenv.config({ override: true }) + const env = dotenv.config({ path: testPath, override: true }) ct.equal(env.parsed.BASIC, 'basic') - ct.equal(process.env.BASIC, '') - // ct.ok(process.env.test) + ct.equal(process.env.BASIC, 'basic') + ct.end() }) t.test('can write to a different object rather than process.env', ct => { - ct.plan(3) - + const testPath = 'tests/.env' process.env.BASIC = 'other' // reset process.env const myObject = {} - const env = dotenv.config({ processEnv: myObject }) + const env = dotenv.config({ path: testPath, processEnv: myObject }) ct.equal(env.parsed.BASIC, 'basic') console.log('logging', process.env.BASIC) ct.equal(process.env.BASIC, 'other') ct.equal(myObject.BASIC, 'basic') + + ct.end() }) t.test('returns parsed object', ct => { - ct.plan(2) - - const env = dotenv.config() + const testPath = 'tests/.env' + const env = dotenv.config({ path: testPath }) ct.notOk(env.error) - ct.same(env.parsed, mockParseResponse) + ct.equal(env.parsed.BASIC, 'basic') + + ct.end() }) t.test('returns any errors thrown from reading file or parsing', ct => { - ct.plan(1) - const readFileSyncStub = sinon.stub(fs, 'readFileSync').returns('test=foo') readFileSyncStub.throws() @@ -172,6 +185,8 @@ t.test('returns any errors thrown from reading file or parsing', ct => { ct.type(env.error, Error) readFileSyncStub.restore() + + ct.end() }) t.test('logs any errors thrown from reading file or parsing when in debug mode', ct => { From 6b829d2551c6b90d1d7d67d46471d845083b1fff Mon Sep 17 00:00:00 2001 From: Scott Motte Date: Wed, 24 Jan 2024 10:34:01 -0800 Subject: [PATCH 3/3] demonstrate currently failing (pending) test. multiple env files should merge --- tests/test-config.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/test-config.js b/tests/test-config.js index 1a24c6bc..fc420476 100644 --- a/tests/test-config.js +++ b/tests/test-config.js @@ -41,6 +41,27 @@ t.test('takes two or more files in the array for path option', ct => { ct.end() }) +t.test('sets values from both .env.local and .env. first file key wins.', { skip: true }, ct => { + delete process.env.SINGLE_QUOTES + + const testPath = ['tests/.env.local', 'tests/.env'] + const env = dotenv.config({ path: testPath }) + + // in both files - first file wins (.env.local) + ct.equal(env.parsed.BASIC, 'local_basic') + ct.equal(process.env.BASIC, 'local_basic') + + // in .env.local only + ct.equal(env.parsed.LOCAL, 'local') + ct.equal(process.env.LOCAL, 'local') + + // in .env only + ct.equal(env.parsed.SINGLE_QUOTES, 'single_quotes') + ct.equal(process.env.SINGLE_QUOTES, 'single_quotes') + + ct.end() +}) + t.test('takes URL for path option', ct => { const envPath = path.resolve(__dirname, '.env') const fileUrl = new URL(`file://${envPath}`)