From 7108c642f4ff6dc5f0c4d30b8a8960064ff8e90f Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Fri, 31 Dec 2021 01:52:28 +0100 Subject: test: Add tests for main package - Changed jest to run from the root package and reference the packages as projects. This required moving the base jest config file away from the project root. - Module isolation seems to prevent ts-jest from loading the shared package, so we disabled it for now. - To better facilitate mocking, services should be split into interfaces and implementation - Had to downgrade to chald 4.1.2 as per https://github.com/chalk/chalk/releases/tag/v5.0.0 at least until https://github.com/microsoft/TypeScript/issues/46452 is resolved. --- .../main/src/services/ConfigPersistenceService.ts | 106 +---------------- .../services/impl/ConfigPersistenceServiceImpl.ts | 128 +++++++++++++++++++++ 2 files changed, 133 insertions(+), 101 deletions(-) create mode 100644 packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts (limited to 'packages/main/src/services') diff --git a/packages/main/src/services/ConfigPersistenceService.ts b/packages/main/src/services/ConfigPersistenceService.ts index b2109f6..b3ad162 100644 --- a/packages/main/src/services/ConfigPersistenceService.ts +++ b/packages/main/src/services/ConfigPersistenceService.ts @@ -17,112 +17,16 @@ * * SPDX-License-Identifier: AGPL-3.0-only */ -import { watch } from 'fs'; -import { readFile, stat, writeFile } from 'fs/promises'; -import JSON5 from 'json5'; -import throttle from 'lodash-es/throttle'; -import { join } from 'path'; import type { ConfigSnapshotOut } from '../stores/Config'; -import { Disposer, getLogger } from '../utils'; - -const log = getLogger('configPersistence'); +import { Disposer } from '../utils'; export type ReadConfigResult = { found: true; data: unknown; } | { found: false; }; -export class ConfigPersistenceService { - private readonly configFilePath: string; - - private writingConfig = false; - - private timeLastWritten: Date | null = null; - - constructor( - private readonly userDataDir: string, - private readonly configFileName: string = 'config.json5', - ) { - this.configFileName = configFileName; - this.configFilePath = join(this.userDataDir, this.configFileName); - } - - async readConfig(): Promise { - let configStr; - try { - configStr = await readFile(this.configFilePath, 'utf8'); - } catch (err) { - if ((err as NodeJS.ErrnoException).code === 'ENOENT') { - log.debug('Config file', this.configFilePath, 'was not found'); - return { found: false }; - } - throw err; - } - log.info('Read config file', this.configFilePath); - return { - found: true, - data: JSON5.parse(configStr), - }; - } - - async writeConfig(configSnapshot: ConfigSnapshotOut): Promise { - const configJson = JSON5.stringify(configSnapshot, { - space: 2, - }); - this.writingConfig = true; - try { - await writeFile(this.configFilePath, configJson, 'utf8'); - const { mtime } = await stat(this.configFilePath); - log.trace('Config file', this.configFilePath, 'last written at', mtime); - this.timeLastWritten = mtime; - } finally { - this.writingConfig = false; - } - log.info('Wrote config file', this.configFilePath); - } - - watchConfig(callback: () => Promise, throttleMs: number): Disposer { - log.debug('Installing watcher for', this.userDataDir); - - const configChanged = throttle(async () => { - let mtime: Date; - try { - const stats = await stat(this.configFilePath); - mtime = stats.mtime; - log.trace('Config file last modified at', mtime); - } catch (err) { - if ((err as NodeJS.ErrnoException).code === 'ENOENT') { - log.debug('Config file', this.configFilePath, 'was deleted after being changed'); - return; - } - throw err; - } - if (!this.writingConfig - && (this.timeLastWritten === null || mtime > this.timeLastWritten)) { - log.debug( - 'Found a config file modified at', - mtime, - 'whish is newer than last written', - this.timeLastWritten, - ); - return callback(); - } - }, throttleMs); - - const watcher = watch(this.userDataDir, { - persistent: false, - }); +export interface ConfigPersistenceService { + readConfig(): Promise; - watcher.on('change', (eventType, filename) => { - if (eventType === 'change' - && (filename === this.configFileName || filename === null)) { - configChanged()?.catch((err) => { - console.log('Unhandled error while listening for config changes', err); - }); - } - }); + writeConfig(configSnapshot: ConfigSnapshotOut): Promise; - return () => { - log.trace('Removing watcher for', this.configFilePath); - watcher.close(); - }; - } + watchConfig(callback: () => Promise, throttleMs: number): Disposer; } diff --git a/packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts b/packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts new file mode 100644 index 0000000..bffc38c --- /dev/null +++ b/packages/main/src/services/impl/ConfigPersistenceServiceImpl.ts @@ -0,0 +1,128 @@ + +/* + * Copyright (C) 2021-2022 Kristóf Marussy + * + * This file is part of Sophie. + * + * Sophie is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import { watch } from 'fs'; +import { readFile, stat, writeFile } from 'fs/promises'; +import JSON5 from 'json5'; +import throttle from 'lodash-es/throttle'; +import { join } from 'path'; + +import type { ConfigPersistenceService, ReadConfigResult } from '../ConfigPersistenceService'; +import type { ConfigSnapshotOut } from '../../stores/Config'; +import { Disposer, getLogger } from '../../utils'; + +const log = getLogger('configPersistence'); + +export class ConfigPersistenceServiceImpl implements ConfigPersistenceService { + private readonly configFilePath: string; + + private writingConfig = false; + + private timeLastWritten: Date | null = null; + + constructor( + private readonly userDataDir: string, + private readonly configFileName: string = 'config.json5', + ) { + this.configFileName = configFileName; + this.configFilePath = join(this.userDataDir, this.configFileName); + } + + async readConfig(): Promise { + let configStr; + try { + configStr = await readFile(this.configFilePath, 'utf8'); + } catch (err) { + if ((err as NodeJS.ErrnoException).code === 'ENOENT') { + log.debug('Config file', this.configFilePath, 'was not found'); + return { found: false }; + } + throw err; + } + log.info('Read config file', this.configFilePath); + return { + found: true, + data: JSON5.parse(configStr), + }; + } + + async writeConfig(configSnapshot: ConfigSnapshotOut): Promise { + const configJson = JSON5.stringify(configSnapshot, { + space: 2, + }); + this.writingConfig = true; + try { + await writeFile(this.configFilePath, configJson, 'utf8'); + const { mtime } = await stat(this.configFilePath); + log.trace('Config file', this.configFilePath, 'last written at', mtime); + this.timeLastWritten = mtime; + } finally { + this.writingConfig = false; + } + log.info('Wrote config file', this.configFilePath); + } + + watchConfig(callback: () => Promise, throttleMs: number): Disposer { + log.debug('Installing watcher for', this.userDataDir); + + const configChanged = throttle(async () => { + let mtime: Date; + try { + const stats = await stat(this.configFilePath); + mtime = stats.mtime; + log.trace('Config file last modified at', mtime); + } catch (err) { + if ((err as NodeJS.ErrnoException).code === 'ENOENT') { + log.debug('Config file', this.configFilePath, 'was deleted after being changed'); + return; + } + throw err; + } + if (!this.writingConfig + && (this.timeLastWritten === null || mtime > this.timeLastWritten)) { + log.debug( + 'Found a config file modified at', + mtime, + 'whish is newer than last written', + this.timeLastWritten, + ); + return callback(); + } + }, throttleMs); + + const watcher = watch(this.userDataDir, { + persistent: false, + }); + + watcher.on('change', (eventType, filename) => { + if (eventType === 'change' + && (filename === this.configFileName || filename === null)) { + configChanged()?.catch((err) => { + console.log('Unhandled error while listening for config changes', err); + }); + } + }); + + return () => { + log.trace('Removing watcher for', this.configFilePath); + watcher.close(); + }; + } +} -- cgit v1.2.3-54-g00ecf