Repository: parse-community/Parse-SDK-JS
Pull request #684 (2018-10-21 12:33:06)

Save Method for Parse.Config
Allow to use `.save()` on Parse.Config when a masterKey is defined to avoid the use of `Parse._request` #684 

- [x] Write Method
- [x] Write Test
- [x] Valid the `save()` then `get()` strategy @flovilmart ?
- [x] Write Documentation Guide
- [ ] Write Documentation API

codecov[bot]: # [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/684?src=pr&el=h1) Report
> Merging [#684](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/684?src=pr&el=desc) into [master](https://codecov.io/gh/parse-community/Parse-SDK-JS/commit/0cc49781b23125759473f7e050d82e1143f9c878?src=pr&el=desc) will **decrease** coverage by `0.37%`.
> The diff coverage is `0%`.

[![Impacted file tree graph](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/684/graphs/tree.svg?width=650&token=8nCENM0QHj&height=150&src=pr)](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/684?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #684      +/-   ##
==========================================
- Coverage   85.69%   85.32%   -0.38%     
==========================================
  Files          48       48              
  Lines        3880     3897      +17     
  Branches      882      884       +2     
==========================================
  Hits         3325     3325              
- Misses        555      572      +17
```


| [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/684?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/ParseConfig.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/684/diff?src=pr&el=tree#diff-c3JjL1BhcnNlQ29uZmlnLmpz) | `66.66% <0%> (-17.71%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/684?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/684?src=pr&el=footer). Last update [0cc4978...b8bd00b](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/684?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

Moumouls: Not passed values can't be overriden during the`.save()` according to the documentation : [Parse Rest Guide Config](https://docs.parseplatform.org/rest/guide/#config)
It's a Parse-Server test and not a Parse SDK test ?
Moumouls: I just wrote the guide doc, the API doc is missing
[PR Docs JS Guide Config](https://github.com/parse-community/docs/pull/573)
Moumouls: What's missing here for merge ?
flovilmart: Nothing, I just merged back master and was waiting for the CI to complete!

Thanks!

Patch:
diff --git a/src/CoreManager.js b/src/CoreManager.js
index 1b9a8d0..65ba665 100644
--- a/src/CoreManager.js
+++ b/src/CoreManager.js
@@ -207,7 +207,7 @@ module.exports = {
   },
 
   setConfigController(controller: ConfigController) {
-    requireMethods('ConfigController', ['current', 'get'], controller);
+    requireMethods('ConfigController', ['current', 'get', 'save'], controller);
     config['ConfigController'] = controller;
   },
 
diff --git a/src/ParseConfig.js b/src/ParseConfig.js
index c10fbe5..7a1ec3b 100644
--- a/src/ParseConfig.js
+++ b/src/ParseConfig.js
@@ -11,6 +11,7 @@
 
 import CoreManager from './CoreManager';
 import decode from './decode';
+import encode from './encode';
 import escape from './escape';
 import ParseError from './ParseError';
 import Storage from './Storage';
@@ -66,7 +67,7 @@ class ParseConfig {
    *     exists, else an empty Parse.Config.
    */
   static current() {
-    var controller = CoreManager.getConfigController();
+    const controller = CoreManager.getConfigController();
     return controller.current();
   }
 
@@ -77,9 +78,25 @@ class ParseConfig {
    *     configuration object when the get completes.
    */
   static get() {
-    var controller = CoreManager.getConfigController();
+    const controller = CoreManager.getConfigController();
     return controller.get();
   }
+
+  /**
+   * Save value keys to the server.
+   * @static
+   * @return {Promise} A promise that is resolved with a newly-created
+   *     configuration object or with the current with the update.
+   */
+  static save(attrs) {
+    const controller = CoreManager.getConfigController();
+    //To avoid a mismatch with the local and the cloud config we get a new version
+    return controller.save(attrs).then(() => {
+      return controller.get();
+    },(error) => {
+      return Promise.reject(error);
+    });
+  }
 }
 
 var currentConfig = null;
@@ -88,7 +105,7 @@ var CURRENT_CONFIG_KEY = 'currentConfig';
 
 function decodePayload(data) {
   try {
-    var json = JSON.parse(data);
+    const json = JSON.parse(data);
     if (json && typeof json === 'object') {
       return decode(json);
     }
@@ -103,14 +120,14 @@ var DefaultController = {
       return currentConfig;
     }
 
-    var config = new ParseConfig();
-    var storagePath = Storage.generatePath(CURRENT_CONFIG_KEY);
-    var configData;
+    const config = new ParseConfig();
+    const storagePath = Storage.generatePath(CURRENT_CONFIG_KEY);
+    let configData;
     if (!Storage.async()) {
       configData = Storage.getItem(storagePath);
 
       if (configData) {
-        var attributes = decodePayload(configData);
+        const attributes = decodePayload(configData);
         if (attributes) {
           config.attributes = attributes;
           currentConfig = config;
@@ -121,7 +138,7 @@ var DefaultController = {
     // Return a promise for async storage controllers
     return Storage.getItemAsync(storagePath).then((configData) => {
       if (configData) {
-        var attributes = decodePayload(configData);
+        const attributes = decodePayload(configData);
         if (attributes) {
           config.attributes = attributes;
           currentConfig = config;
@@ -132,22 +149,22 @@ var DefaultController = {
   },
 
   get() {
-    var RESTController = CoreManager.getRESTController();
+    const RESTController = CoreManager.getRESTController();
 
     return RESTController.request(
       'GET', 'config', {}, {}
     ).then((response) => {
       if (!response || !response.params) {
-        var error = new ParseError(
+        const error = new ParseError(
           ParseError.INVALID_JSON,
           'Config JSON response invalid.'
         );
         return Promise.reject(error);
       }
 
-      var config = new ParseConfig();
+      const config = new ParseConfig();
       config.attributes = {};
-      for (var attr in response.params) {
+      for (const attr in response.params) {
         config.attributes[attr] = decode(response.params[attr]);
       }
       currentConfig = config;
@@ -158,6 +175,30 @@ var DefaultController = {
         return config;
       });
     });
+  },
+
+  save(attrs) {
+    var RESTController = CoreManager.getRESTController();
+    const encodedAttrs = {};
+    for(const key in attrs){
+      encodedAttrs[key] = encode(attrs[key])
+    }
+    return RESTController.request(
+      'PUT',
+      'config',
+      { params: encodedAttrs },
+      { useMasterKey: true }
+    ).then(response => {
+      if(response && response.result){
+        return Promise.resolve()
+      } else {
+        const error = new ParseError(
+          ParseError.INTERNAL_SERVER_ERROR,
+          'Error occured updating Config.'
+        );
+        return Promise.reject(error)
+      }
+    })
   }
 };
 


Test patch:
diff --git a/src/__tests__/CoreManager-test.js b/src/__tests__/CoreManager-test.js
index 381a4e6..a57fc7c 100644
--- a/src/__tests__/CoreManager-test.js
+++ b/src/__tests__/CoreManager-test.js
@@ -87,14 +87,16 @@ describe('CoreManager', () => {
 
     expect(CoreManager.setConfigController.bind(null, {
       current: function() {},
-      get: function() {}
+      get: function() {},
+      save : function() {}
     })).not.toThrow();
   });
 
   it('can set and get ConfigController', () => {
     const controller = {
       current: function() {},
-      get: function() {}
+      get: function() {},
+      save : function() {}
     };
 
     CoreManager.setConfigController(controller);
diff --git a/src/__tests__/ParseConfig-test.js b/src/__tests__/ParseConfig-test.js
index 25440de..be88361 100644
--- a/src/__tests__/ParseConfig-test.js
+++ b/src/__tests__/ParseConfig-test.js
@@ -100,6 +100,48 @@ describe('ParseConfig', () => {
     });
   });
 
+  it('can save a config object with masterkey', (done) => {
+    //Load a request that match the get() & save() request
+    CoreManager.setRESTController({
+      request() {
+        return Promise.resolve({
+          params : {
+            str : 'hello2',
+            num : 46
+          },
+          result: true
+        });
+      },
+      ajax() {}
+    });
+    ParseConfig.save({str: 'hello2','num':46}).then((config) => {
+      expect(config.get('str')).toBe('hello2');
+      expect(config.get('num')).toBe(46);
+      const path = Storage.generatePath('currentConfig');
+      expect(JSON.parse(Storage.getItem(path))).toEqual({
+        str: 'hello2',
+        num: 46
+      });
+      done();
+    });
+  });
+
+  it('rejects save on invalid response', (done) => {
+    CoreManager.setRESTController({
+      request() {
+        return Promise.resolve({result: false});
+      },
+      ajax() {}
+    });
+    ParseConfig.save({str: 'hello2','num':46}).then((config) => {
+      expect(config).toBe(1)
+      done();
+    },(error) => {
+      expect(error.code).toBe(1)
+      done();
+    });
+  });
+
   it('rejects the promise when an invalid payload comes back', (done) => {
     CoreManager.setRESTController({
       request() {
@@ -110,7 +152,6 @@ describe('ParseConfig', () => {
     ParseConfig.get().then(null, (error) => {
       expect(error.code).toBe(107);
       expect(error.message).toBe('Config JSON response invalid.');
-
       done();
     });
   });