aboutsummaryrefslogtreecommitdiffstats
path: root/packages/main/src/controllers/__tests__/initConfig.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/main/src/controllers/__tests__/initConfig.spec.ts')
-rw-r--r--packages/main/src/controllers/__tests__/initConfig.spec.ts185
1 files changed, 185 insertions, 0 deletions
diff --git a/packages/main/src/controllers/__tests__/initConfig.spec.ts b/packages/main/src/controllers/__tests__/initConfig.spec.ts
new file mode 100644
index 0000000..e386a07
--- /dev/null
+++ b/packages/main/src/controllers/__tests__/initConfig.spec.ts
@@ -0,0 +1,185 @@
1/*
2 * Copyright (C) 2021-2022 Kristóf Marussy <kristof@marussy.com>
3 *
4 * This file is part of Sophie.
5 *
6 * Sophie is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, version 3.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: AGPL-3.0-only
19 */
20
21import { jest } from '@jest/globals';
22import { mocked } from 'jest-mock';
23import ms from 'ms';
24
25import type ConfigPersistenceService from '../../services/ConfigPersistenceService';
26import { Config, config as configModel } from '../../stores/Config';
27import type Disposer from '../../utils/Disposer';
28import { silenceLogger } from '../../utils/log';
29import initConfig from '../initConfig';
30
31let config: Config;
32const persistenceService: ConfigPersistenceService = {
33 readConfig: jest.fn(),
34 writeConfig: jest.fn(),
35 watchConfig: jest.fn(),
36};
37const lessThanThrottleMs = ms('0.1s');
38const throttleMs = ms('1s');
39
40beforeAll(() => {
41 jest.useFakeTimers();
42 silenceLogger();
43});
44
45beforeEach(() => {
46 config = configModel.create();
47});
48
49describe('when initializing', () => {
50 describe('when there is no config file', () => {
51 beforeEach(() => {
52 mocked(persistenceService.readConfig).mockResolvedValueOnce({
53 found: false,
54 });
55 });
56
57 it('should create a new config file', async () => {
58 await initConfig(config, persistenceService);
59 expect(persistenceService.writeConfig).toBeCalledTimes(1);
60 });
61
62 it('should bail if there is an an error creating the config file', async () => {
63 mocked(persistenceService.writeConfig).mockRejectedValue(new Error('boo'));
64 await expect(() => initConfig(config, persistenceService)).rejects.toBeInstanceOf(Error);
65 });
66 });
67
68 describe('when there is a valid config file', () => {
69 beforeEach(() => {
70 mocked(persistenceService.readConfig).mockResolvedValueOnce({
71 found: true,
72 data: {
73 themeSource: 'dark',
74 },
75 });
76 });
77
78 it('should read the existing config file is there is one', async () => {
79 await initConfig(config, persistenceService);
80 expect(persistenceService.writeConfig).not.toBeCalled();
81 expect(config.themeSource).toBe('dark');
82 });
83
84 it('should bail if it cannot set up a watcher', async () => {
85 mocked(persistenceService.watchConfig).mockImplementationOnce(() => {
86 throw new Error('boo');
87 });
88 await expect(() => initConfig(config, persistenceService)).rejects.toBeInstanceOf(Error);
89 });
90 });
91
92 it('should not apply an invalid config file', async () => {
93 mocked(persistenceService.readConfig).mockResolvedValueOnce({
94 found: true,
95 data: {
96 themeSource: -1,
97 },
98 });
99 await initConfig(config, persistenceService);
100 expect(config.themeSource).not.toBe(-1);
101 });
102
103 it('should bail if it cannot determine whether there is a config file', async () => {
104 mocked(persistenceService.readConfig).mockRejectedValue(new Error('boo'));
105 await expect(() => initConfig(config, persistenceService)).rejects.toBeInstanceOf(Error);
106 });
107});
108
109describe('when it has loaded the config', () => {
110 let sutDisposer: Disposer;
111 const watcherDisposer: Disposer = jest.fn();
112 let configChangedCallback: () => Promise<void>;
113
114 beforeEach(async () => {
115 mocked(persistenceService.readConfig).mockResolvedValueOnce({
116 found: true,
117 data: {},
118 });
119 mocked(persistenceService.watchConfig).mockReturnValueOnce(watcherDisposer);
120 sutDisposer = await initConfig(config, persistenceService, throttleMs);
121 [[configChangedCallback]] = mocked(persistenceService.watchConfig).mock.calls;
122 jest.resetAllMocks();
123 });
124
125 it('should throttle saving changes to the config file', () => {
126 mocked(persistenceService.writeConfig).mockResolvedValue(undefined);
127 config.setThemeSource('dark');
128 jest.advanceTimersByTime(lessThanThrottleMs);
129 config.setThemeSource('light');
130 jest.advanceTimersByTime(throttleMs);
131 expect(persistenceService.writeConfig).toBeCalledTimes(1);
132 });
133
134 it('should handle config writing errors gracefully', () => {
135 mocked(persistenceService.writeConfig).mockRejectedValue(new Error('boo'));
136 config.setThemeSource('dark');
137 jest.advanceTimersByTime(throttleMs);
138 expect(persistenceService.writeConfig).toBeCalledTimes(1);
139 });
140
141 it('should read the config file when it has changed', async () => {
142 mocked(persistenceService.readConfig).mockResolvedValueOnce({
143 found: true,
144 data: {
145 themeSource: 'dark',
146 },
147 });
148 await configChangedCallback();
149 // Do not write back the changes we have just read.
150 expect(persistenceService.writeConfig).not.toBeCalled();
151 expect(config.themeSource).toBe('dark');
152 });
153
154 it('should not apply an invalid config file when it has changed', async () => {
155 mocked(persistenceService.readConfig).mockResolvedValueOnce({
156 found: true,
157 data: {
158 themeSource: -1,
159 },
160 });
161 await configChangedCallback();
162 expect(config.themeSource).not.toBe(-1);
163 });
164
165 it('should handle config writing errors gracefully', async () => {
166 mocked(persistenceService.readConfig).mockRejectedValue(new Error('boo'));
167 await configChangedCallback();
168 });
169
170 describe('when it was disposed', () => {
171 beforeEach(() => {
172 sutDisposer();
173 });
174
175 it('should dispose the watcher', () => {
176 expect(watcherDisposer).toBeCalled();
177 });
178
179 it('should not listen to store changes any more', () => {
180 config.setThemeSource('dark');
181 jest.advanceTimersByTime(2 * throttleMs);
182 expect(persistenceService.writeConfig).not.toBeCalled();
183 });
184 });
185});