Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update tests to better handle multiple env files passed to path #798

Merged
merged 3 commits into from
Jan 24, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 120 additions & 102 deletions tests/test-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,197 +7,214 @@ 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()
delete process.env.BASIC // reset
})

t.test('takes string for path option', ct => {
ct.plan(1)

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')

ct.end()
})

t.test('takes string for path option', ct => {
ct.plan(1)
t.test('takes array for path option', ct => {
const testPath = ['tests/.env']
const env = dotenv.config({ path: testPath })

const testPath = 'tests/.env'
dotenv.config({ path: testPath })
ct.equal(env.parsed.BASIC, 'basic')
ct.equal(process.env.BASIC, 'basic')

ct.equal(readFileSyncStub.args[0][0], testPath)
ct.end()
})

t.test('takes array for path option', ct => {
ct.plan(1)
t.test('takes two or more files in the array for path option', ct => {
const testPath = ['tests/.env.local', 'tests/.env']
const env = dotenv.config({ path: testPath })

const testPath = ['tests/.env']
dotenv.config({ path: testPath })
ct.equal(env.parsed.BASIC, 'local_basic')
ct.equal(process.env.BASIC, 'local_basic')

ct.equal(readFileSyncStub.args[0][0], testPath)
ct.end()
})

t.test('takes two or more files in the array for path option', ct => {
ct.plan(1)
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']
dotenv.config({ path: testPath })
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')

ct.equal(readFileSyncStub.args[0][0], testPath)
// 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 => {
ct.plan(1)
const envPath = path.resolve(__dirname, '.env')
const fileUrl = new URL(`file://${envPath}`)

const testPath = new URL('file://home/user/project/.env')
dotenv.config({ path: testPath })
const env = dotenv.config({ path: fileUrl })

ct.equal(env.parsed.BASIC, 'basic')
ct.equal(process.env.BASIC, 'basic')

ct.equal(readFileSyncStub.args[0][0], testPath)
ct.end()
})

t.test('takes option for path along with home directory char ~', ct => {
ct.plan(2)
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 })

ct.equal(readFileSyncStub.args[0][0], path.join(mockedHomedir, '.env'))
ct.ok(homedirStub.called)

homedirStub.restore()
readFileSyncStub.restore()
ct.end()
})

t.test('takes option for encoding', ct => {
ct.plan(1)
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 res = dotenv.config()

ct.same(res.parsed, mockParseResponse)
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.test = existing
// 'foo' returned as value in `beforeEach`. should keep this 'bar'
const env = dotenv.config()
process.env.BASIC = existing
const env = dotenv.config({ path: testPath })

ct.equal(env.parsed.BASIC, 'basic')
ct.equal(process.env.BASIC, existing)

ct.equal(env.parsed && env.parsed.test, mockParseResponse.test)
ct.equal(process.env.test, 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.test = existing
// 'foo' returned as value in `beforeEach`. should keep this 'bar'
const env = dotenv.config({ override: true })

ct.equal(env.parsed && env.parsed.test, mockParseResponse.test)
ct.equal(process.env.test, 'foo')
})

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()

ct.equal(env.parsed && env.parsed.test, mockParseResponse.test)
// NB: process.env.test becomes undefined on Windows
ct.notOk(process.env.test)
}
)

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 })

ct.equal(env.parsed && env.parsed.test, mockParseResponse.test)
// NB: process.env.test becomes undefined on Windows
ct.ok(process.env.test)
}
)
process.env.BASIC = existing
const env = dotenv.config({ path: testPath, override: true })

t.test('can write to a different object rather than process.env', ct => {
ct.plan(3)
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 => {
const testPath = 'tests/.env'
const existing = ''
process.env.BASIC = existing
const env = dotenv.config({ path: testPath })

process.env.test = 'other' // reset process.env
ct.equal(env.parsed.BASIC, 'basic')
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 => {
const testPath = 'tests/.env'
const existing = ''
process.env.BASIC = existing
const env = dotenv.config({ path: testPath, override: true })

ct.equal(env.parsed.BASIC, 'basic')
ct.equal(process.env.BASIC, 'basic')
ct.end()
})

t.test('can write to a different object rather than process.env', ct => {
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.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.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()
const env = dotenv.config()

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 => {
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 })
Expand All @@ -206,6 +223,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 => {
Expand Down