Skip to content

Commit 993ff20

Browse files
mamasodrew-gross
mamaso
authored andcommitted
Added settings manager (#1645)
* Added settings manager * Clarity improvements & test fixes for settings manager * Updated cli-defs, features router, removed global test object * Fixed cli definitions syntax error * Fixed formatting issues * Removed ENABLE_CONFIG_CHANGES env var & improved doc * Disable config changes for most tests * test fixes * Correct handling of verbose
1 parent 4462145 commit 993ff20

14 files changed

+512
-163
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"body-parser": "^1.14.2",
2525
"colors": "^1.1.2",
2626
"commander": "^2.9.0",
27+
"deep-equal": "^1.0.1",
2728
"deepcopy": "^0.6.1",
2829
"express": "^4.13.4",
2930
"intersect": "^1.0.1",

spec/FileLoggerAdapter.spec.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
var FileLoggerAdapter = require('../src/Adapters/Logger/FileLoggerAdapter').FileLoggerAdapter;
22
var Parse = require('parse/node').Parse;
3+
var configureLogger = require('../src/logger').configureLogger;
34

4-
describe('info logs', () => {
5+
describe('FileLoggerAdapter', () => {
6+
beforeEach(() => {
7+
configureLogger({ logsFolder: './test_logs/file_logger/'});
8+
});
59

610
it("Verify INFO logs", (done) => {
711
var fileLoggerAdapter = new FileLoggerAdapter();
@@ -20,9 +24,6 @@ describe('info logs', () => {
2024
});
2125
});
2226
});
23-
});
24-
25-
describe('error logs', () => {
2627

2728
it("Verify ERROR logs", (done) => {
2829
var fileLoggerAdapter = new FileLoggerAdapter();
@@ -42,4 +43,4 @@ describe('error logs', () => {
4243
});
4344
});
4445
});
45-
});
46+
});

spec/PersistentSettings.spec.js

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
var request = require('request');
2+
var deepcopy = require('deepcopy');
3+
var Config = require('../src/Config');
4+
var logger = require('../src/logger').default;
5+
6+
var settingsCollectionName = '_ServerSettings';
7+
var configuration;
8+
var settingsCollection;
9+
var parseServerObject;
10+
11+
describe('Persistent Settings', () => {
12+
beforeEach((done) => {
13+
configuration = deepcopy(defaultConfiguration);
14+
configuration.enableConfigChanges = true;
15+
newServer().then(done);
16+
});
17+
18+
describe('Upon Initialization', () => {
19+
it('should persist settings', (done) => {
20+
configuration.clientKey = 'local';
21+
22+
newServer()
23+
.then(getPersisted)
24+
.then(persisted => {
25+
expect(persisted.clientKey).toEqual('local');
26+
})
27+
.then(done)
28+
.catch(done.fail);
29+
});
30+
31+
it('should only load mutable settings from database', (done) => {
32+
configuration.clientKey = 'local'; // defined
33+
34+
updatePersisted({ logLevel: 'info', clientKey: 'persisted' })
35+
.then(newServer)
36+
.then(_ => {
37+
var config = parseServerObject.config;
38+
expect(config.logLevel).toEqual('info'); // not locked or defined, so updated
39+
expect(config.clientKey).toEqual('local'); // configuration defined, therefore not updated
40+
})
41+
.then(done)
42+
.catch(done.fail);
43+
});
44+
45+
it('overwrites defined settings if lockDefinedSettings is false', (done) => {
46+
configuration.clientKey = 'local';
47+
configuration.lockDefinedSettings = false;
48+
49+
updatePersisted({ clientKey: 'persisted' })
50+
.then(newServer)
51+
.then(_ => {
52+
var config = parseServerObject.config;
53+
expect(config.clientKey).toEqual('persisted'); // defined setting was updated
54+
})
55+
.then(done)
56+
.catch(done.fail);
57+
});
58+
});
59+
60+
describe('Settings Router', () => {
61+
it('should provide error on post if config changes disabled', (done) => {
62+
configuration.enableConfigChanges = false;
63+
newServer()
64+
.then(endpoint.get)
65+
.then(res => expect(res.res.statusCode).toBe(200))
66+
.then(_ => endpoint.post({ clientKey: 'causesError' }))
67+
.then(res => {
68+
expect(res.res.statusCode).toBe(400);
69+
expect(res.body.code).toBe(119); //Parse.Error.OPERATION_FORBIDDEN
70+
expect(res.body.error).toBe('Server config changes are disabled');
71+
})
72+
.then(done)
73+
.catch(done.fail);
74+
});
75+
76+
it('should run setting callbacks such as configureLogger', (done) => {
77+
endpoint.post({ logLevel: 'debug' })
78+
.then(res => {
79+
expect(res.res.statusCode).toBe(200);
80+
expect(res.body.logLevel).toBe('debug');
81+
expect(logger.transports['parse-server'].level).toBe('debug');
82+
})
83+
.then(endpoint.get)
84+
.then(res => {
85+
expect(res.res.statusCode).toBe(200);
86+
expect(res.body.logLevel).toBe('debug');
87+
})
88+
.then(done)
89+
.catch(done.fail);
90+
});
91+
92+
it('should not set defined setting', (done) => {
93+
endpoint.post({ clientKey: 'alreadyDefined' })
94+
.then(res => {
95+
expect(res.res.statusCode).toBe(200);
96+
expect(res.body.clientKey).toBeUndefined();
97+
})
98+
.then(endpoint.get)
99+
.then(res => {
100+
expect(res.res.statusCode).toBe(200);
101+
expect(res.body.clientKey).toBe(configuration.clientKey);
102+
})
103+
.then(done)
104+
.catch(done.fail);
105+
});
106+
107+
it('should not allow access without masterKey', (done) => {
108+
var invalidHeaders = {
109+
'X-Parse-Application-Id': 'test',
110+
'X-Parse-Master-Key': 'invalid'
111+
};
112+
113+
endpoint.post({ logLevel: 'silly' }, invalidHeaders)
114+
.then(res => {
115+
expect(res.res.statusCode).toBe(403);
116+
expect(res.body.error).toBe('unauthorized');
117+
})
118+
.then(_ => endpoint.get(invalidHeaders))
119+
.then(res => {
120+
expect(res.res.statusCode).toBe(403);
121+
expect(res.body.error).toBe('unauthorized');
122+
})
123+
.then(done)
124+
.catch(done.fail);
125+
});
126+
127+
it('should expose non-existant settings as null', (done) => {
128+
delete configuration.clientKey;
129+
130+
settingsCollection.drop()
131+
.then(newServer)
132+
.then(endpoint.get)
133+
.then(res => expect(res.body.clientKey).toBe(null))
134+
.then(done)
135+
.catch(done.fail);
136+
});
137+
138+
it('should fetch database values', (done) => {
139+
delete configuration.clientKey;
140+
141+
settingsCollection.drop()
142+
.then(newServer)
143+
.then(endpoint.get)
144+
.then(res => expect(res.body.clientKey).toBe(null))
145+
.then(_ => updatePersisted({ clientKey: 'persisted' }))
146+
.then(endpoint.get)
147+
.then(res => expect(res.body.clientKey).toBe('persisted'))
148+
.then(done)
149+
.catch(done.fail);
150+
});
151+
152+
it('should only return modified values', (done) => {
153+
// info is default log level
154+
var currentLogLevel;
155+
endpoint.get()
156+
.then(res => currentLogLevel = res.body.logLevel)
157+
.then(_ => endpoint.post({ logLevel: currentLogLevel }))
158+
.then(res => expect(res.body.logLevel).toBeUndefined)
159+
.then(done)
160+
.catch(done.fail);
161+
});
162+
});
163+
});
164+
165+
function newServer() {
166+
parseServerObject = setServerConfiguration(deepcopy(configuration));
167+
return parseServerObject.config.settingsInitialized
168+
.then(_ => {
169+
var config = new Config(configuration.appId);
170+
return config.database.adapter.adaptiveCollection(settingsCollectionName);
171+
})
172+
.then(coll => { settingsCollection = coll; });
173+
}
174+
175+
function updatePersisted(settings) {
176+
settings.applicationId = configuration.appId;
177+
return parseServerObject.config.settingsInitialized
178+
.then(_ => settingsCollection.upsertOne({ applicationId: configuration.appId }, { $set: settings }))
179+
.then(_ => undefined);
180+
}
181+
182+
function getPersisted() {
183+
return parseServerObject.config.settingsInitialized
184+
.then(_ => settingsCollection.find({ applicationId: configuration.appId }))
185+
.then(results => results && results.length && results[0]);
186+
}
187+
188+
var settingsUrl = 'http://localhost:8378/1/settings';
189+
var defaultHeaders = {
190+
'X-Parse-Application-Id': 'test',
191+
'X-Parse-Master-Key': 'test'
192+
};
193+
194+
var req = (method, headers, body) => new Promise((resolve, reject) => {
195+
request[method]({
196+
url: settingsUrl,
197+
json: body,
198+
headers: headers || defaultHeaders
199+
}, (err, res, body) => {
200+
if (err) {
201+
reject(err);
202+
} else {
203+
if (typeof body === 'string') body = JSON.parse(body);
204+
resolve({
205+
res: res,
206+
body: body
207+
});
208+
}
209+
});
210+
});
211+
212+
var endpoint = {
213+
get: headers => req('get', headers),
214+
post: (body, headers) => req('post', headers, body)
215+
}

spec/PublicAPI.spec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ describe("public API", () => {
1414
masterKey: 'test',
1515
collectionPrefix: 'test_',
1616
fileKey: 'test',
17-
publicServerURL: 'http://localhost:8378/1'
17+
publicServerURL: 'http://localhost:8378/1',
18+
enableConfigChanges: false
1819
});
1920
done();
2021
})

spec/SettingsRouter.spec.js

Lines changed: 0 additions & 57 deletions
This file was deleted.

spec/helper.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ var cache = require('../src/cache').default;
66
var DatabaseAdapter = require('../src/DatabaseAdapter');
77
var express = require('express');
88
var facebook = require('../src/authDataManager/facebook');
9-
var ParseServer = require('../src/index').ParseServer;
9+
var ParseServer = require('../src/index').default;
1010
var path = require('path');
1111

1212
var databaseURI = process.env.DATABASE_URI;
@@ -39,13 +39,14 @@ var defaultConfiguration = {
3939
myoauth: {
4040
module: path.resolve(__dirname, "myoauth") // relative path as it's run from src
4141
}
42-
}
42+
},
43+
enableConfigChanges: false
4344
};
4445

4546
// Set up a default API server for testing with default configuration.
46-
var api = new ParseServer(defaultConfiguration);
47+
var parseServer = new ParseServer(defaultConfiguration);
4748
var app = express();
48-
app.use('/1', api);
49+
app.use('/1', parseServer.app);
4950
var server = app.listen(port);
5051

5152
// Prevent reinitializing the server from clobbering Cloud Code
@@ -63,9 +64,10 @@ var setServerConfiguration = configuration => {
6364
server.close();
6465
cache.clearCache();
6566
app = express();
66-
api = new ParseServer(configuration);
67-
app.use('/1', api);
67+
parseServer = new ParseServer(configuration);
68+
app.use('/1', parseServer.app);
6869
server = app.listen(port);
70+
return parseServer;
6971
};
7072

7173
var restoreServerConfiguration = () => setServerConfiguration(defaultConfiguration);
@@ -140,15 +142,19 @@ function notWorking() {}
140142
function ok(bool, message) {
141143
expect(bool).toBeTruthy(message);
142144
}
145+
143146
function equal(a, b, message) {
144147
expect(a).toEqual(b, message);
145148
}
149+
146150
function strictEqual(a, b, message) {
147151
expect(a).toBe(b, message);
148152
}
153+
149154
function notEqual(a, b, message) {
150155
expect(a).not.toEqual(b, message);
151156
}
157+
152158
function expectSuccess(params) {
153159
return {
154160
success: params.success,
@@ -158,6 +164,7 @@ function expectSuccess(params) {
158164
},
159165
}
160166
}
167+
161168
function expectError(errorCode, callback) {
162169
return {
163170
success: function(result) {
@@ -277,4 +284,4 @@ jasmine.restoreLibrary = function(library, name) {
277284
throw 'Can not find library ' + library + ' ' + name;
278285
}
279286
require(library)[name] = libraryCache[library][name];
280-
}
287+
}

0 commit comments

Comments
 (0)