mirror of
https://github.com/Cameri/nostream.git
synced 2025-09-20 13:01:22 +02:00
test: refactor settings
This commit is contained in:
@@ -2,13 +2,14 @@ import { Duplex, EventEmitter } from 'stream'
|
|||||||
import { IncomingMessage, Server, ServerResponse } from 'http'
|
import { IncomingMessage, Server, ServerResponse } from 'http'
|
||||||
import packageJson from '../../package.json'
|
import packageJson from '../../package.json'
|
||||||
|
|
||||||
|
import { ISettings } from '../@types/settings'
|
||||||
import { IWebServerAdapter } from '../@types/adapters'
|
import { IWebServerAdapter } from '../@types/adapters'
|
||||||
import { Settings } from '../utils/settings'
|
|
||||||
|
|
||||||
export class WebServerAdapter extends EventEmitter implements IWebServerAdapter {
|
export class WebServerAdapter extends EventEmitter implements IWebServerAdapter {
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
private readonly webServer: Server,
|
private readonly webServer: Server,
|
||||||
|
private readonly settings: () => ISettings,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.webServer.on('request', this.onWebServerRequest.bind(this))
|
this.webServer.on('request', this.onWebServerRequest.bind(this))
|
||||||
@@ -25,7 +26,7 @@ export class WebServerAdapter extends EventEmitter implements IWebServerAdapter
|
|||||||
if (request.method === 'GET' && request.headers['accept'] === 'application/nostr+json') {
|
if (request.method === 'GET' && request.headers['accept'] === 'application/nostr+json') {
|
||||||
const {
|
const {
|
||||||
info: { name, description, pubkey, contact },
|
info: { name, description, pubkey, contact },
|
||||||
} = Settings
|
} = this.settings()
|
||||||
|
|
||||||
const relayInformationDocument = {
|
const relayInformationDocument = {
|
||||||
name,
|
name,
|
||||||
|
@@ -5,6 +5,7 @@ import { IWebSocketAdapter, IWebSocketServerAdapter } from '../@types/adapters'
|
|||||||
import { WebSocketAdapterEvent, WebSocketServerAdapterEvent } from '../constants/adapter'
|
import { WebSocketAdapterEvent, WebSocketServerAdapterEvent } from '../constants/adapter'
|
||||||
import { Event } from '../@types/event'
|
import { Event } from '../@types/event'
|
||||||
import { Factory } from '../@types/base'
|
import { Factory } from '../@types/base'
|
||||||
|
import { ISettings } from '../@types/settings'
|
||||||
import { propEq } from 'ramda'
|
import { propEq } from 'ramda'
|
||||||
import { WebServerAdapter } from './web-server-adapter'
|
import { WebServerAdapter } from './web-server-adapter'
|
||||||
|
|
||||||
@@ -22,9 +23,10 @@ export class WebSocketServerAdapter extends WebServerAdapter implements IWebSock
|
|||||||
private readonly createWebSocketAdapter: Factory<
|
private readonly createWebSocketAdapter: Factory<
|
||||||
IWebSocketAdapter,
|
IWebSocketAdapter,
|
||||||
[WebSocket, IncomingMessage, IWebSocketServerAdapter]
|
[WebSocket, IncomingMessage, IWebSocketServerAdapter]
|
||||||
>
|
>,
|
||||||
|
settings: () => ISettings,
|
||||||
) {
|
) {
|
||||||
super(webServer)
|
super(webServer, settings)
|
||||||
|
|
||||||
this.webSocketsAdapters = new WeakMap()
|
this.webSocketsAdapters = new WeakMap()
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { IncomingMessage, MessageType } from '../@types/messages'
|
import { IncomingMessage, MessageType } from '../@types/messages'
|
||||||
|
import { createSettings } from './settings-factory'
|
||||||
import { DelegatedEventMessageHandler } from '../handlers/delegated-event-message-handler'
|
import { DelegatedEventMessageHandler } from '../handlers/delegated-event-message-handler'
|
||||||
import { delegatedEventStrategyFactory } from './delegated-event-strategy-factory'
|
import { delegatedEventStrategyFactory } from './delegated-event-strategy-factory'
|
||||||
import { EventMessageHandler } from '../handlers/event-message-handler'
|
import { EventMessageHandler } from '../handlers/event-message-handler'
|
||||||
@@ -6,7 +7,6 @@ import { eventStrategyFactory } from './event-strategy-factory'
|
|||||||
import { IEventRepository } from '../@types/repositories'
|
import { IEventRepository } from '../@types/repositories'
|
||||||
import { isDelegatedEvent } from '../utils/event'
|
import { isDelegatedEvent } from '../utils/event'
|
||||||
import { IWebSocketAdapter } from '../@types/adapters'
|
import { IWebSocketAdapter } from '../@types/adapters'
|
||||||
import { Settings } from '../utils/settings'
|
|
||||||
import { SubscribeMessageHandler } from '../handlers/subscribe-message-handler'
|
import { SubscribeMessageHandler } from '../handlers/subscribe-message-handler'
|
||||||
import { UnsubscribeMessageHandler } from '../handlers/unsubscribe-message-handler'
|
import { UnsubscribeMessageHandler } from '../handlers/unsubscribe-message-handler'
|
||||||
|
|
||||||
@@ -17,13 +17,17 @@ export const messageHandlerFactory = (
|
|||||||
case MessageType.EVENT:
|
case MessageType.EVENT:
|
||||||
{
|
{
|
||||||
if (isDelegatedEvent(message[1])) {
|
if (isDelegatedEvent(message[1])) {
|
||||||
return new DelegatedEventMessageHandler(adapter, delegatedEventStrategyFactory(eventRepository), Settings)
|
return new DelegatedEventMessageHandler(
|
||||||
|
adapter,
|
||||||
|
delegatedEventStrategyFactory(eventRepository),
|
||||||
|
createSettings
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return new EventMessageHandler(adapter, eventStrategyFactory(eventRepository), Settings)
|
return new EventMessageHandler(adapter, eventStrategyFactory(eventRepository), createSettings)
|
||||||
}
|
}
|
||||||
case MessageType.REQ:
|
case MessageType.REQ:
|
||||||
return new SubscribeMessageHandler(adapter, eventRepository)
|
return new SubscribeMessageHandler(adapter, eventRepository, createSettings)
|
||||||
case MessageType.CLOSE:
|
case MessageType.CLOSE:
|
||||||
return new UnsubscribeMessageHandler(adapter,)
|
return new UnsubscribeMessageHandler(adapter,)
|
||||||
default:
|
default:
|
||||||
|
4
src/factories/settings-factory.ts
Normal file
4
src/factories/settings-factory.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { ISettings } from '../@types/settings'
|
||||||
|
import { SettingsStatic } from '../utils/settings'
|
||||||
|
|
||||||
|
export const createSettings = (): ISettings => SettingsStatic.createSettings()
|
@@ -13,7 +13,7 @@ export class EventMessageHandler implements IMessageHandler {
|
|||||||
public constructor(
|
public constructor(
|
||||||
protected readonly webSocket: IWebSocketAdapter,
|
protected readonly webSocket: IWebSocketAdapter,
|
||||||
protected readonly strategyFactory: Factory<IEventStrategy<Event, Promise<void>>, [Event, IWebSocketAdapter]>,
|
protected readonly strategyFactory: Factory<IEventStrategy<Event, Promise<void>>, [Event, IWebSocketAdapter]>,
|
||||||
private readonly settings: ISettings
|
private readonly settings: () => ISettings
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
public async handleMessage(message: IncomingEventMessage): Promise<void> {
|
public async handleMessage(message: IncomingEventMessage): Promise<void> {
|
||||||
@@ -49,7 +49,7 @@ export class EventMessageHandler implements IMessageHandler {
|
|||||||
|
|
||||||
protected canAcceptEvent(event: Event): string | undefined {
|
protected canAcceptEvent(event: Event): string | undefined {
|
||||||
const now = Math.floor(Date.now()/1000)
|
const now = Math.floor(Date.now()/1000)
|
||||||
const limits = this.settings.limits.event
|
const limits = this.settings().limits.event
|
||||||
if (limits.createdAt.maxPositiveDelta > 0) {
|
if (limits.createdAt.maxPositiveDelta > 0) {
|
||||||
if (event.created_at > now + limits.createdAt.maxPositiveDelta) {
|
if (event.created_at > now + limits.createdAt.maxPositiveDelta) {
|
||||||
return `created_at is more than ${limits.createdAt.maxPositiveDelta} seconds in the future`
|
return `created_at is more than ${limits.createdAt.maxPositiveDelta} seconds in the future`
|
||||||
|
@@ -8,8 +8,8 @@ import { streamEach, streamEnd, streamFilter, streamMap } from '../utils/stream'
|
|||||||
import { SubscriptionFilter, SubscriptionId } from '../@types/subscription'
|
import { SubscriptionFilter, SubscriptionId } from '../@types/subscription'
|
||||||
import { Event } from '../@types/event'
|
import { Event } from '../@types/event'
|
||||||
import { IEventRepository } from '../@types/repositories'
|
import { IEventRepository } from '../@types/repositories'
|
||||||
|
import { ISettings } from '../@types/settings'
|
||||||
import { IWebSocketAdapter } from '../@types/adapters'
|
import { IWebSocketAdapter } from '../@types/adapters'
|
||||||
import { Settings } from '../utils/settings'
|
|
||||||
import { SubscribeMessage } from '../@types/messages'
|
import { SubscribeMessage } from '../@types/messages'
|
||||||
import { WebSocketAdapterEvent } from '../constants/adapter'
|
import { WebSocketAdapterEvent } from '../constants/adapter'
|
||||||
|
|
||||||
@@ -19,6 +19,7 @@ export class SubscribeMessageHandler implements IMessageHandler, IAbortable {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private readonly webSocket: IWebSocketAdapter,
|
private readonly webSocket: IWebSocketAdapter,
|
||||||
private readonly eventRepository: IEventRepository,
|
private readonly eventRepository: IEventRepository,
|
||||||
|
private readonly settings: () => ISettings,
|
||||||
) {
|
) {
|
||||||
this.abortController = new AbortController()
|
this.abortController = new AbortController()
|
||||||
}
|
}
|
||||||
@@ -66,7 +67,7 @@ export class SubscribeMessageHandler implements IMessageHandler, IAbortable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private canSubscribe(subscriptionId: string, filters: SubscriptionFilter[]): string | undefined {
|
private canSubscribe(subscriptionId: string, filters: SubscriptionFilter[]): string | undefined {
|
||||||
const maxSubscriptions = Settings.limits.client.subscription.maxSubscriptions
|
const maxSubscriptions = this.settings().limits.client.subscription.maxSubscriptions
|
||||||
if (maxSubscriptions > 0) {
|
if (maxSubscriptions > 0) {
|
||||||
const subscriptions = this.webSocket.getSubscriptions()
|
const subscriptions = this.webSocket.getSubscriptions()
|
||||||
if (!subscriptions.has(subscriptionId) && subscriptions.size + 1 > maxSubscriptions) {
|
if (!subscriptions.has(subscriptionId) && subscriptions.size + 1 > maxSubscriptions) {
|
||||||
@@ -74,7 +75,7 @@ export class SubscribeMessageHandler implements IMessageHandler, IAbortable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxFilters = Settings.limits.client.subscription.maxFilters
|
const maxFilters = this.settings().limits.client.subscription.maxFilters
|
||||||
if (maxFilters > 0) {
|
if (maxFilters > 0) {
|
||||||
if (filters.length > maxFilters) {
|
if (filters.length > maxFilters) {
|
||||||
return `Too many filters: Number of filters per susbscription must be less or equal to ${maxFilters}`
|
return `Too many filters: Number of filters per susbscription must be less or equal to ${maxFilters}`
|
||||||
|
@@ -4,6 +4,7 @@ import http from 'http'
|
|||||||
import process from 'process'
|
import process from 'process'
|
||||||
import { WebSocketServer } from 'ws'
|
import { WebSocketServer } from 'ws'
|
||||||
|
|
||||||
|
import { createSettings } from './factories/settings-factory'
|
||||||
import { EventRepository } from './repositories/event-repository'
|
import { EventRepository } from './repositories/event-repository'
|
||||||
import { getDbClient } from './database/client'
|
import { getDbClient } from './database/client'
|
||||||
import packageJson from '../package.json'
|
import packageJson from '../package.json'
|
||||||
@@ -64,7 +65,8 @@ if (cluster.isPrimary) {
|
|||||||
const adapter = new WebSocketServerAdapter(
|
const adapter = new WebSocketServerAdapter(
|
||||||
server,
|
server,
|
||||||
wss,
|
wss,
|
||||||
webSocketAdapterFactory(eventRepository)
|
webSocketAdapterFactory(eventRepository),
|
||||||
|
createSettings,
|
||||||
)
|
)
|
||||||
|
|
||||||
adapter.listen(port)
|
adapter.listen(port)
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { existsSync, readFileSync, writeFileSync } from 'fs'
|
import fs from 'fs'
|
||||||
import { homedir } from 'os'
|
import { homedir } from 'os'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { mergeDeepRight } from 'ramda'
|
import { mergeDeepRight } from 'ramda'
|
||||||
@@ -6,85 +6,94 @@ import { mergeDeepRight } from 'ramda'
|
|||||||
import { ISettings } from '../@types/settings'
|
import { ISettings } from '../@types/settings'
|
||||||
import packageJson from '../../package.json'
|
import packageJson from '../../package.json'
|
||||||
|
|
||||||
export const getSettingsFilePath = (filename = 'settings.json'): string => join(
|
export class SettingsStatic {
|
||||||
process.env.NOSTR_CONFIG_DIR ?? join(homedir(), '.nostr'),
|
static _settings: ISettings
|
||||||
filename,
|
|
||||||
)
|
|
||||||
|
|
||||||
let _settings: ISettings
|
public static getSettingsFilePath(filename = 'settings.json') {
|
||||||
|
return join(
|
||||||
|
process.env.NOSTR_CONFIG_DIR ?? join(homedir(), '.nostr'),
|
||||||
|
filename
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const getDefaultSettings = (): ISettings => ({
|
public static getDefaultSettings(): ISettings {
|
||||||
info: {
|
return {
|
||||||
relay_url: `wss://${packageJson.name}.your-domain.com`,
|
info: {
|
||||||
name: `${packageJson.name}.your-domain.com`,
|
relay_url: 'wss://nostr-ts-relay.your-domain.com',
|
||||||
description: packageJson.description,
|
name: `${packageJson.name}.your-domain.com`,
|
||||||
pubkey: '',
|
description: packageJson.description,
|
||||||
contact: 'operator@your-domain.com',
|
pubkey: 'replace-with-your-pubkey',
|
||||||
},
|
contact: 'operator@your-domain.com',
|
||||||
limits: {
|
|
||||||
event: {
|
|
||||||
eventId: {
|
|
||||||
minLeadingZeroBits: 0,
|
|
||||||
},
|
},
|
||||||
kind: {
|
limits: {
|
||||||
whitelist: [],
|
event: {
|
||||||
blacklist: [],
|
eventId: {
|
||||||
|
minLeadingZeroBits: 0,
|
||||||
|
},
|
||||||
|
kind: {
|
||||||
|
whitelist: [],
|
||||||
|
blacklist: [],
|
||||||
|
},
|
||||||
|
pubkey: {
|
||||||
|
minLeadingZeroBits: 0,
|
||||||
|
whitelist: [],
|
||||||
|
blacklist: [],
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
maxPositiveDelta: 900,
|
||||||
|
maxNegativeDelta: 0, // disabled
|
||||||
|
},
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
subscription: {
|
||||||
|
maxSubscriptions: 10,
|
||||||
|
maxFilters: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
pubkey: {
|
}
|
||||||
minLeadingZeroBits: 0,
|
}
|
||||||
whitelist: [],
|
|
||||||
blacklist: [],
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
maxPositiveDelta: 900, // +15 min
|
|
||||||
maxNegativeDelta: 0, // disabled
|
|
||||||
},
|
|
||||||
},
|
|
||||||
client: {
|
|
||||||
subscription: {
|
|
||||||
maxSubscriptions: 10,
|
|
||||||
maxFilters: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadSettings = (path: string) => {
|
public static loadSettings(path: string) {
|
||||||
return JSON.parse(
|
return JSON.parse(
|
||||||
readFileSync(
|
fs.readFileSync(
|
||||||
|
path,
|
||||||
|
{ encoding: 'utf-8' }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createSettings(): ISettings {
|
||||||
|
if (SettingsStatic._settings) {
|
||||||
|
return SettingsStatic._settings
|
||||||
|
}
|
||||||
|
const path = SettingsStatic.getSettingsFilePath()
|
||||||
|
const defaults = SettingsStatic.getDefaultSettings()
|
||||||
|
try {
|
||||||
|
|
||||||
|
if (fs.existsSync(path)) {
|
||||||
|
SettingsStatic._settings = mergeDeepRight(
|
||||||
|
defaults,
|
||||||
|
SettingsStatic.loadSettings(path)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
SettingsStatic.saveSettings(path, defaults)
|
||||||
|
SettingsStatic._settings = mergeDeepRight({}, defaults)
|
||||||
|
}
|
||||||
|
|
||||||
|
return SettingsStatic._settings
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Unable to read config file. Reason: %s', error.message)
|
||||||
|
|
||||||
|
return defaults
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static saveSettings(path: string, settings: ISettings) {
|
||||||
|
return fs.writeFileSync(
|
||||||
path,
|
path,
|
||||||
{ encoding: 'utf-8' },
|
JSON.stringify(settings, null, 2),
|
||||||
),
|
{ encoding: 'utf-8' }
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
const createSettings = (): ISettings => {
|
|
||||||
const path = getSettingsFilePath()
|
|
||||||
const defaults = getDefaultSettings()
|
|
||||||
try {
|
|
||||||
if (_settings) {
|
|
||||||
return _settings
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!existsSync(path)) {
|
|
||||||
saveSettings(path, defaults)
|
|
||||||
}
|
|
||||||
|
|
||||||
_settings = mergeDeepRight(defaults, loadSettings(path))
|
|
||||||
|
|
||||||
return _settings
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Unable to read config file. Reason: %s', error.message)
|
|
||||||
|
|
||||||
return defaults
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const saveSettings = (path: string, settings: ISettings) => {
|
|
||||||
return writeFileSync(
|
|
||||||
path,
|
|
||||||
JSON.stringify(settings, null, 2),
|
|
||||||
{ encoding: 'utf-8' }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
export const Settings = createSettings()
|
|
||||||
|
24
test/unit/factories/settings-factory.spec.ts
Normal file
24
test/unit/factories/settings-factory.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { expect } from 'chai'
|
||||||
|
import Sinon from 'sinon'
|
||||||
|
|
||||||
|
import { createSettings } from '../../../src/factories/settings-factory'
|
||||||
|
import { SettingsStatic } from '../../../src/utils/settings'
|
||||||
|
|
||||||
|
describe('getSettings', () => {
|
||||||
|
let createSettingsStub: Sinon.SinonStub
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
createSettingsStub = Sinon.stub(SettingsStatic, 'createSettings')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
createSettingsStub.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls createSettings and returns', () => {
|
||||||
|
const settings = Symbol()
|
||||||
|
createSettingsStub.returns(settings)
|
||||||
|
|
||||||
|
expect(createSettings()).to.equal(settings)
|
||||||
|
})
|
||||||
|
})
|
@@ -64,7 +64,7 @@ describe('EventMessageHandler', () => {
|
|||||||
handler = new EventMessageHandler(
|
handler = new EventMessageHandler(
|
||||||
webSocket as any,
|
webSocket as any,
|
||||||
strategyFactoryStub,
|
strategyFactoryStub,
|
||||||
{} as any,
|
() => ({}) as any,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ describe('EventMessageHandler', () => {
|
|||||||
handler = new EventMessageHandler(
|
handler = new EventMessageHandler(
|
||||||
{} as any,
|
{} as any,
|
||||||
() => null,
|
() => null,
|
||||||
settings,
|
() => settings,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
import { expect } from 'chai'
|
import { expect } from 'chai'
|
||||||
|
import fs from 'fs'
|
||||||
import { homedir } from 'os'
|
import { homedir } from 'os'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
|
import Sinon from 'sinon'
|
||||||
|
|
||||||
import { getDefaultSettings, getSettingsFilePath } from '../../../src/utils/settings'
|
import { SettingsStatic } from '../../../src/utils/settings'
|
||||||
|
|
||||||
describe('Settings', () => {
|
describe('SettingsStatic', () => {
|
||||||
describe('getSettingsFilePath', () => {
|
describe('.getSettingsFilePath', () => {
|
||||||
let originalEnv: NodeJS.ProcessEnv
|
let originalEnv: NodeJS.ProcessEnv
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -18,40 +20,39 @@ describe('Settings', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('returns string ending with settings.json by default', () => {
|
it('returns string ending with settings.json by default', () => {
|
||||||
expect(getSettingsFilePath()).to.be.a('string').and.to.match(/settings\.json$/)
|
expect(SettingsStatic.getSettingsFilePath()).to.be.a('string').and.to.match(/settings\.json$/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns string ending with given string', () => {
|
it('returns string ending with given string', () => {
|
||||||
expect(getSettingsFilePath('ending')).to.be.a('string').and.to.match(/ending$/)
|
expect(SettingsStatic.getSettingsFilePath('ending')).to.be.a('string').and.to.match(/ending$/)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns path begins with user\'s home dir by default', () => {
|
it('returns path begins with user\'s home dir by default', () => {
|
||||||
expect(getSettingsFilePath()).to.be.a('string').and.equal(`${join(homedir(), '.nostr')}/settings.json`)
|
expect(SettingsStatic.getSettingsFilePath()).to.be.a('string').and.equal(`${join(homedir(), '.nostr')}/settings.json`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns path with NOSTR_CONFIG_DIR if set', () => {
|
it('returns path with NOSTR_CONFIG_DIR if set', () => {
|
||||||
process.env.NOSTR_CONFIG_DIR = '/some/path'
|
process.env.NOSTR_CONFIG_DIR = '/some/path'
|
||||||
|
|
||||||
expect(getSettingsFilePath()).to.be.a('string').and.equal('/some/path/settings.json')
|
expect(SettingsStatic.getSettingsFilePath()).to.be.a('string').and.equal('/some/path/settings.json')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getDefaultSettings', () => {
|
describe('.getDefaultSettings', () => {
|
||||||
it('returns object with info', () => {
|
it('returns object with info', () => {
|
||||||
expect(getDefaultSettings())
|
expect(SettingsStatic.getDefaultSettings())
|
||||||
.to.have.property('info')
|
.to.have.property('info')
|
||||||
.and.to.deep.equal({
|
.and.to.deep.equal({
|
||||||
relay_url: 'wss://nostr-ts-relay.your-domain.com',
|
relay_url: 'wss://nostr-ts-relay.your-domain.com',
|
||||||
name: 'nostr-ts-relay.your-domain.com',
|
name: 'nostr-ts-relay.your-domain.com',
|
||||||
description: 'A nostr relay written in Typescript.',
|
description: 'A nostr relay written in Typescript.',
|
||||||
pubkey: '',
|
pubkey: 'replace-with-your-pubkey',
|
||||||
contact: 'operator@your-domain.com',
|
contact: 'operator@your-domain.com',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
it('returns object with default limits', () => {
|
it('returns object with default limits', () => {
|
||||||
expect(getDefaultSettings())
|
expect(SettingsStatic.getDefaultSettings())
|
||||||
.to.have.property('limits')
|
.to.have.property('limits')
|
||||||
.and.to.deep.equal({
|
.and.to.deep.equal({
|
||||||
event: {
|
event: {
|
||||||
@@ -82,15 +83,156 @@ describe('Settings', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// describe('loadSettings', () => {
|
describe('.loadSettings', () => {
|
||||||
|
let readFileSyncStub: Sinon.SinonStub
|
||||||
|
|
||||||
// })
|
beforeEach(() => {
|
||||||
|
readFileSyncStub = Sinon.stub(fs, 'readFileSync')
|
||||||
|
})
|
||||||
|
|
||||||
// describe('createSettings', () => {
|
afterEach(() => {
|
||||||
|
readFileSyncStub.restore()
|
||||||
|
})
|
||||||
|
|
||||||
// })
|
it('loads settings from given path', () => {
|
||||||
|
readFileSyncStub.returns('"content"')
|
||||||
|
|
||||||
// describe('saveSettings', () => {
|
expect(SettingsStatic.loadSettings('/some/path')).to.equal('content')
|
||||||
|
|
||||||
// })
|
expect(readFileSyncStub).to.have.been.calledOnceWithExactly(
|
||||||
})
|
'/some/path',
|
||||||
|
{ encoding: 'utf-8' }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.createSettings', () => {
|
||||||
|
let existsSyncStub: Sinon.SinonStub
|
||||||
|
let getSettingsFilePathStub: Sinon.SinonStub
|
||||||
|
let getDefaultSettingsStub: Sinon.SinonStub
|
||||||
|
let saveSettingsStub: Sinon.SinonStub
|
||||||
|
let loadSettingsStub: Sinon.SinonStub
|
||||||
|
|
||||||
|
let sandbox: Sinon.SinonSandbox
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
SettingsStatic._settings = undefined
|
||||||
|
|
||||||
|
sandbox = Sinon.createSandbox()
|
||||||
|
|
||||||
|
existsSyncStub = sandbox.stub(fs, 'existsSync')
|
||||||
|
getSettingsFilePathStub = sandbox.stub(SettingsStatic, 'getSettingsFilePath')
|
||||||
|
getDefaultSettingsStub = sandbox.stub(SettingsStatic, 'getDefaultSettings')
|
||||||
|
saveSettingsStub = sandbox.stub(SettingsStatic, 'saveSettings')
|
||||||
|
loadSettingsStub = sandbox.stub(SettingsStatic, 'loadSettings')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('creates settings from default if settings file is missing', () => {
|
||||||
|
getDefaultSettingsStub.returns({})
|
||||||
|
getSettingsFilePathStub.returns('/some/path/settings.json')
|
||||||
|
existsSyncStub.returns(false)
|
||||||
|
|
||||||
|
expect(SettingsStatic.createSettings()).to.deep.equal({})
|
||||||
|
|
||||||
|
expect(existsSyncStub).to.have.been.calledOnceWithExactly('/some/path/settings.json')
|
||||||
|
expect(getSettingsFilePathStub).to.have.been.calledOnce
|
||||||
|
expect(getDefaultSettingsStub).to.have.been.calledOnce
|
||||||
|
expect(saveSettingsStub).to.have.been.calledOnceWithExactly(
|
||||||
|
'/some/path/settings.json',
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
expect(loadSettingsStub).not.to.have.been.called
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns default settings if saving settings file throws', () => {
|
||||||
|
const error = new Error('mistakes were made')
|
||||||
|
const defaults = Symbol()
|
||||||
|
getSettingsFilePathStub.returns('/some/path/settings.json')
|
||||||
|
getDefaultSettingsStub.returns(defaults)
|
||||||
|
saveSettingsStub.throws(error)
|
||||||
|
existsSyncStub.returns(false)
|
||||||
|
|
||||||
|
expect(SettingsStatic.createSettings()).to.equal(defaults)
|
||||||
|
|
||||||
|
expect(existsSyncStub).to.have.been.calledOnceWithExactly('/some/path/settings.json')
|
||||||
|
expect(getSettingsFilePathStub).to.have.been.calledOnce
|
||||||
|
expect(getDefaultSettingsStub).to.have.been.calledOnce
|
||||||
|
expect(saveSettingsStub).to.have.been.calledOnceWithExactly(
|
||||||
|
'/some/path/settings.json',
|
||||||
|
defaults,
|
||||||
|
)
|
||||||
|
expect(loadSettingsStub).not.to.have.been.called
|
||||||
|
})
|
||||||
|
|
||||||
|
it('loads settings from file if settings file is exists', () => {
|
||||||
|
getDefaultSettingsStub.returns({})
|
||||||
|
loadSettingsStub.returns({})
|
||||||
|
getSettingsFilePathStub.returns('/some/path/settings.json')
|
||||||
|
existsSyncStub.returns(true)
|
||||||
|
|
||||||
|
expect(SettingsStatic.createSettings()).to.deep.equal({})
|
||||||
|
|
||||||
|
expect(existsSyncStub).to.have.been.calledOnceWithExactly('/some/path/settings.json')
|
||||||
|
expect(getSettingsFilePathStub).to.have.been.calledOnce
|
||||||
|
expect(getDefaultSettingsStub).to.have.been.calledOnce
|
||||||
|
expect(saveSettingsStub).not.to.have.been.called
|
||||||
|
expect(loadSettingsStub).to.have.been.calledOnceWithExactly('/some/path/settings.json')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns defaults if loading settings file throws', () => {
|
||||||
|
const defaults = Symbol()
|
||||||
|
const error = new Error('mistakes were made')
|
||||||
|
getDefaultSettingsStub.returns(defaults)
|
||||||
|
loadSettingsStub.throws(error)
|
||||||
|
getSettingsFilePathStub.returns('/some/path/settings.json')
|
||||||
|
existsSyncStub.returns(true)
|
||||||
|
|
||||||
|
expect(SettingsStatic.createSettings()).to.equal(defaults)
|
||||||
|
|
||||||
|
expect(existsSyncStub).to.have.been.calledOnceWithExactly('/some/path/settings.json')
|
||||||
|
expect(getSettingsFilePathStub).to.have.been.calledOnce
|
||||||
|
expect(getDefaultSettingsStub).to.have.been.calledOnce
|
||||||
|
expect(saveSettingsStub).not.to.have.been.called
|
||||||
|
expect(loadSettingsStub).to.have.been.calledOnceWithExactly('/some/path/settings.json')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns cached settings if set', () => {
|
||||||
|
const cachedSettings = Symbol()
|
||||||
|
SettingsStatic._settings = cachedSettings as any
|
||||||
|
|
||||||
|
expect(SettingsStatic.createSettings()).to.equal(cachedSettings)
|
||||||
|
|
||||||
|
expect(getSettingsFilePathStub).not.to.have.been.calledOnce
|
||||||
|
expect(getDefaultSettingsStub).not.to.have.been.calledOnce
|
||||||
|
expect(existsSyncStub).not.to.have.been.called
|
||||||
|
expect(saveSettingsStub).not.to.have.been.called
|
||||||
|
expect(loadSettingsStub).not.to.have.been.called
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('.saveSettings', () => {
|
||||||
|
let writeFileSyncStub: Sinon.SinonStub
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
writeFileSyncStub = Sinon.stub(fs, 'writeFileSync')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
writeFileSyncStub.restore()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('saves settings to given path', () => {
|
||||||
|
SettingsStatic.saveSettings('/some/path/settings.json', {key: 'value'} as any)
|
||||||
|
|
||||||
|
expect(writeFileSyncStub).to.have.been.calledOnceWithExactly(
|
||||||
|
'/some/path/settings.json',
|
||||||
|
'{\n "key": "value"\n}',
|
||||||
|
{ encoding: 'utf-8' }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Reference in New Issue
Block a user