96 lines
2.7 KiB
TypeScript
96 lines
2.7 KiB
TypeScript
import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
import { loadConfig } from '../src/server/config.js'
|
|
|
|
const secret = 'test-secret-that-is-at-least-thirty-two-characters'
|
|
const directories: string[] = []
|
|
|
|
afterEach(() => {
|
|
for (const directory of directories.splice(0)) {
|
|
rmSync(directory, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
describe('loadConfig', () => {
|
|
it('normalizes a valid production origin', () => {
|
|
expect(
|
|
loadConfig({
|
|
NODE_ENV: 'production',
|
|
APP_SECRET: secret,
|
|
PUBLIC_ORIGIN: 'https://feedback.example.com/'
|
|
})
|
|
).toMatchObject({
|
|
nodeEnv: 'production',
|
|
publicOrigin: 'https://feedback.example.com'
|
|
})
|
|
})
|
|
|
|
it('requires a production HTTPS origin without paths or credentials', () => {
|
|
expect(() =>
|
|
loadConfig({
|
|
NODE_ENV: 'production',
|
|
APP_SECRET: secret
|
|
})
|
|
).toThrow()
|
|
expect(() =>
|
|
loadConfig({
|
|
NODE_ENV: 'production',
|
|
APP_SECRET: secret,
|
|
PUBLIC_ORIGIN: 'http://feedback.example.com'
|
|
})
|
|
).toThrow()
|
|
expect(() =>
|
|
loadConfig({
|
|
NODE_ENV: 'production',
|
|
APP_SECRET: secret,
|
|
PUBLIC_ORIGIN: 'https://feedback.example.com/admin'
|
|
})
|
|
).toThrow()
|
|
})
|
|
|
|
it('accepts an explicitly configured secret without a length restriction', () => {
|
|
expect(loadConfig({ APP_SECRET: 'short' }).appSecret).toBe('short')
|
|
})
|
|
|
|
it('generates and reuses a secret when APP_SECRET is omitted', () => {
|
|
const directory = mkdtempSync(join(tmpdir(), 'product-issue-config-'))
|
|
directories.push(directory)
|
|
|
|
const first = loadConfig({ DATA_DIRECTORY: directory })
|
|
const second = loadConfig({ DATA_DIRECTORY: directory })
|
|
|
|
expect(first.appSecret).toHaveLength(43)
|
|
expect(second.appSecret).toBe(first.appSecret)
|
|
expect(readFileSync(join(directory, '.app-secret'), 'utf8').trim()).toBe(
|
|
first.appSecret
|
|
)
|
|
})
|
|
|
|
it('parses initial administrator configuration', () => {
|
|
expect(
|
|
loadConfig({
|
|
APP_SECRET: secret,
|
|
INITIAL_ADMIN_EMAIL: 'admin@example.com',
|
|
INITIAL_ADMIN_PASSWORD: 'a local admin password'
|
|
}).initialAdmin
|
|
).toEqual({
|
|
email: 'admin@example.com',
|
|
displayName: 'Administrator',
|
|
password: 'a local admin password'
|
|
})
|
|
})
|
|
|
|
it('requires initial administrator email and password together', () => {
|
|
expect(() =>
|
|
loadConfig({
|
|
APP_SECRET: secret,
|
|
INITIAL_ADMIN_EMAIL: 'admin@example.com'
|
|
})
|
|
).toThrow(
|
|
'INITIAL_ADMIN_EMAIL and INITIAL_ADMIN_PASSWORD must be configured together'
|
|
)
|
|
})
|
|
})
|