Retrieve resources in two steps
authorFrank Bessou <frank.bessou@logilab.fr>
Fri, 07 Apr 2017 10:35:21 +0200
changeset 94 8f5072d8e015
parent 93 b3c8e9f42ad3
child 95 3b50d57dfc06
Retrieve resources in two steps Resource location is given in the 'self' link of its schema. To retrieve a resource, we now have to first retrieve its schema and follow its 'self' link. If the schema exists but the 'self' link does not exist, the resource data is left null.
src/Api.js
test/index.js
--- a/src/Api.js	Fri Apr 07 16:39:43 2017 +0200
+++ b/src/Api.js	Fri Apr 07 10:35:21 2017 +0200
@@ -1,4 +1,4 @@
-/* global fetch, API_URL, SCRIPT_NAME */
+/* global fetch, API_URL, SCRIPT_NAME, Jsonary*/
 
 import 'whatwg-fetch';
 import {isEmpty} from 'lodash/lang';
@@ -69,23 +69,35 @@
         return this.jsonSchemaFetch(url);
     }
 
-    getResource(resourceUrl) {
-        const schemaPromise = this.getSchema(resourceUrl);
-        const fetchPromise = this.jsonFetchResponse(resourceUrl);
-        const dataPromise = fetchPromise.then( response => response.json() );
-        const allowedActionsPromise = fetchPromise.then( response => this.extractAllowedActions(response) );
+    getResource(resourceRoute) {
+        const resource = {};
+        // Fetch resource schema
+        const schemaPromise = this.getSchema(resourceRoute);
+        const resourcePromise = schemaPromise.then(schema => {
+            const wrappedSchema = Jsonary.createSchema(schema);
+            const selfLink = wrappedSchema.getLink('self');
+            // If the schema does not contain self link
+            // The resource is empty
+            if (!selfLink) {
+                resource.data = wrapEntityData(null, schema);
+                resource.allowedActions = [];
+                return resource;
+            }
+            resource.url = selfLink.uriTemplate.fill();
+            const fetchPromise = this.jsonFetchResponse(resource.url);
+            const dataPromise = fetchPromise.then( response => response.json() );
+            const allowedActionsPromise = fetchPromise.then( response => this.extractAllowedActions(response) );
+            // Load resource data and retrieve allowed actions
+            return Promise.all([dataPromise, allowedActionsPromise]).then(
+                    ([data, allowedActions]) => {
+                        resource.allowedActions = allowedActions;
+                        resource.data = wrapEntityData(data, schema);
+                        return resource;
+                    });
+        });
+        return resourcePromise;
+    }
 
-        const entityPromise = Promise.all([schemaPromise, dataPromise, allowedActionsPromise]).then(
-                ([schema, data, allowedActions]) => {
-                    const wrappedData = wrapEntityData(data, schema);
-                    return {
-                        url: resourceUrl,
-                        data: wrappedData,
-                        allowedActions: allowedActions,
-                    };
-                });
-        return entityPromise;
-    }
 
     getRelatedSchema(etype, rtype, role = 'creation', targetType = null) {
         let url = `/${etype}/relationships/${rtype}/schema?role=${role}`;
--- a/test/index.js	Fri Apr 07 16:39:43 2017 +0200
+++ b/test/index.js	Fri Apr 07 10:35:21 2017 +0200
@@ -6,6 +6,7 @@
 import sinon from 'sinon';
 import {assert} from 'sinon';
 import React from 'react';
+import {merge} from 'lodash/object';
 
 import {buildFormData} from '../src/utils';
 import {Api} from '../src/Api';
@@ -390,17 +391,74 @@
         });
     });
 
+    describe('getResource', () => {
+
+        const baseUrl = 'http://example.com';
+        let api;
+        let fakeFetch;
+        let remoteResource;
+
+        beforeEach(() => {
+            remoteResource = {allow: 'GET', data: {}, schema: {}};
+            fakeFetch = sinon.stub();
+            const options = {headers: {"Content-Type": "application/json"}};
+            const dataOptions = merge({headers: {Allow: remoteResource.allow}}, options)
+            fakeFetch.withArgs(sinon.match(/\/schema$/), sinon.match({}))
+            .callsFake( _ =>
+                    Promise.resolve(new Response(JSON.stringify(remoteResource.schema), options)));
+            fakeFetch.callsFake( _ => Promise.resolve(new Response(JSON.stringify(remoteResource.data), dataOptions)));
+            api = new Api('http://example.com', fakeFetch);
+        });
+
+        it('should fetch schema', done => {
+            api.getResource('').then(resource => {
+                const schema = resource.data.schemas()[0].data.value();
+
+                assert.calledWith(fakeFetch, baseUrl + '/schema');
+                expect(schema).to.deep.equal(remoteResource.schema);
+                done();
+            }).catch(done);
+        });
+
+        it('should not fetch data if the schema does not contain a self link', done => {
+            api.getResource('').then( _ => {
+
+                assert.calledOnce(fakeFetch);
+                assert.calledWith(fakeFetch, baseUrl + '/schema');
+                done();
+            }).catch(done);
+        });
+
+        it('should fetch resource specified in schema\'s self link when it exists', done => {
+            remoteResource.schema = {
+                links: [{
+                    rel: 'self',
+                    href: '/bar',
+                }],
+            }
+
+            api.getResource('/foo').then( _ => {
+
+                assert.calledTwice(fakeFetch);
+                assert.calledWith(fakeFetch, baseUrl + '/foo/schema');
+                assert.calledWith(fakeFetch, baseUrl + '/bar');
+                done();
+            }).catch(done);
+        });
+
+    });
+
     describe('getEntity', () => {
         let api;
         let fakeFetch;
         let etype = 'foo';
         let eid = 123;
-        const schemaJson = JSON.stringify({buzz: "fuzz"});
+        const schemaJson = JSON.stringify({buzz: "fuzz", links: [{rel: 'self', href: '/foo/123'}]});
         const dataJson =JSON.stringify({foo: "bar"});
 
         beforeEach(() => {
             fakeFetch = sinon.stub();
-            const options = {headers: {"Content-Type": "application/json"}};
+            const options = {headers: {"Allow": "GET", "Content-Type": "application/json"}};
             fakeFetch.withArgs(sinon.match(/\/foo\/123$/), sinon.match({}))
                 .resolves(new Response(dataJson, options));
 
@@ -409,12 +467,13 @@
             api = new Api('http://example.com', fakeFetch);
         });
 
-        it('should fetch the entity data', () => {
-            api.getEntity(etype, eid);
+        it('should fetch the entity data', done => {
+            api.getEntity(etype, eid).then( _ => {
 
             const dataUrl = 'http://example.com/foo/123';
-            const initOptions = {headers: {Accept: 'application/json'}};
-            assert.calledWith(fakeFetch, dataUrl, sinon.match(initOptions));
+            assert.calledWith(fakeFetch, dataUrl, sinon.match({}));
+            done();
+            });
         });
 
         it('should fetch the entity schema', () => {