From de8d9124108ee5768559d2ea3165af19dead4adf Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Thu, 25 Jun 2020 14:13:52 -0400 Subject: [PATCH] =?UTF-8?q?fix:=20=F0=9F=90=9B=20prenormalize=20findAll=20?= =?UTF-8?q?responses=20in=20application=20adapter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ember Data fetch-manager will throw an error if an array resposne is an empty object before it reaches the serializer. Thus we need to intercept the response in the adapter and "prenormalize" it to contain an empty items array `{items: []}`. --- addons/api/addon/adapters/application.js | 51 +++++++++++++++++++ addons/api/package.json | 1 + addons/api/tests/dummy/mirage/config.js | 26 ++++++++++ .../tests/dummy/mirage/scenarios/default.js | 9 ++++ .../dummy/mirage/serializers/application.js | 4 ++ .../tests/unit/adapters/application-test.js | 14 +++++ yarn.lock | 15 ++++++ 7 files changed, 120 insertions(+) create mode 100644 addons/api/tests/dummy/mirage/config.js create mode 100644 addons/api/tests/dummy/mirage/scenarios/default.js create mode 100644 addons/api/tests/dummy/mirage/serializers/application.js diff --git a/addons/api/addon/adapters/application.js b/addons/api/addon/adapters/application.js index cef50d39e..06949503e 100644 --- a/addons/api/addon/adapters/application.js +++ b/addons/api/addon/adapters/application.js @@ -6,6 +6,37 @@ import { dasherize } from '@ember/string'; import { pluralize } from 'ember-inflector'; import { isArray } from '@ember/array'; +/** + * Returns false if the payload is an empty object, true otherwise. + * Taken from Ember Data fetch-manager. + * @param {object} adapterPayload + * @return {boolean} + */ +function payloadIsNotBlank(adapterPayload) { + if (Array.isArray(adapterPayload)) { + return true; + } else { + return Object.keys(adapterPayload || {}).length !== 0; + } +} + +/** + * If the resonse is not empty, returns the response. Otherwise, returns + * a new payload containing an empty `items` array, as expected by + * the serializer. + * + * Normally this would be performed 100% inside the serializer. + * However, the fetch manager, which we cannot easily override, glues the + * adapter and serializers together. Before it forwards a response from the + * adapter on to the serializer, it checks for an "empty" response. If the + * response is empty, fetch manager throws and error and never forwards it on. + * @param {object} response + * @return {object} + */ +function prenormalizeArrayResponse(response) { + return payloadIsNotBlank(response) ? response : {items: []}; +} + export default class ApplicationAdapter extends RESTAdapter { // =attributes @@ -36,6 +67,26 @@ export default class ApplicationAdapter extends RESTAdapter { return dasherize(pluralize(type)); } + /** + * Intercepts "empty" responses and adds an empty `items` array. + * @override + * @method findAll + * @return {Promise} promise + */ + findAll() { + return super.findAll(...arguments).then(prenormalizeArrayResponse); + } + + /** + * Intercepts "empty" responses and adds an empty `items` array. + * @override + * @method query + * @return {Promise} promise + */ + query() { + return super.query(...arguments).then(prenormalizeArrayResponse); + } + /** * Overrides default ajax method by rewritting PUT requests as PATCH. * PATCH is the request method used by our API for updates. diff --git a/addons/api/package.json b/addons/api/package.json index 151c4959b..f9a6af815 100644 --- a/addons/api/package.json +++ b/addons/api/package.json @@ -35,6 +35,7 @@ "ember-cli-dependency-checker": "^3.2.0", "ember-cli-eslint": "^5.1.0", "ember-cli-inject-live-reload": "^2.0.2", + "ember-cli-mirage": "^1.1.7", "ember-cli-sri": "^2.1.1", "ember-cli-template-lint": "^1.0.0-beta.3", "ember-cli-uglify": "^3.0.0", diff --git a/addons/api/tests/dummy/mirage/config.js b/addons/api/tests/dummy/mirage/config.js new file mode 100644 index 000000000..c2cd26a9d --- /dev/null +++ b/addons/api/tests/dummy/mirage/config.js @@ -0,0 +1,26 @@ +export default function() { + + // These comments are here to help you get started. Feel free to delete them. + + /* + Config (with defaults). + + Note: these only affect routes defined *after* them! + */ + + // this.urlPrefix = ''; // make this `http://localhost:8080`, for example, if your API is on a different server + // this.namespace = ''; // make this `/api`, for example, if your API is namespaced + // this.timing = 400; // delay for each request, automatically set to 0 during testing + + /* + Shorthand cheatsheet: + + this.get('/posts'); + this.post('/posts'); + this.get('/posts/:id'); + this.put('/posts/:id'); // or this.patch + this.del('/posts/:id'); + + https://www.ember-cli-mirage.com/docs/route-handlers/shorthands + */ +} diff --git a/addons/api/tests/dummy/mirage/scenarios/default.js b/addons/api/tests/dummy/mirage/scenarios/default.js new file mode 100644 index 000000000..adb3af4c0 --- /dev/null +++ b/addons/api/tests/dummy/mirage/scenarios/default.js @@ -0,0 +1,9 @@ +export default function(/* server */) { + + /* + Seed your development database using your factories. + This data will not be loaded in your tests. + */ + + // server.createList('post', 10); +} diff --git a/addons/api/tests/dummy/mirage/serializers/application.js b/addons/api/tests/dummy/mirage/serializers/application.js new file mode 100644 index 000000000..6d47a3669 --- /dev/null +++ b/addons/api/tests/dummy/mirage/serializers/application.js @@ -0,0 +1,4 @@ +import { JSONAPISerializer } from 'ember-cli-mirage'; + +export default JSONAPISerializer.extend({ +}); diff --git a/addons/api/tests/unit/adapters/application-test.js b/addons/api/tests/unit/adapters/application-test.js index 8c58bafec..366c44c4e 100644 --- a/addons/api/tests/unit/adapters/application-test.js +++ b/addons/api/tests/unit/adapters/application-test.js @@ -3,9 +3,12 @@ import { setupTest } from 'ember-qunit'; import config from 'ember-get-config'; import RESTAdapter from '@ember-data/adapter/rest'; import { InvalidError } from '@ember-data/adapter/error'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { Response } from 'miragejs'; module('Unit | Adapter | application', function (hooks) { setupTest(hooks); + setupMirage(hooks); test('its namespace is equal to the configured namespace', function (assert) { assert.expect(2); @@ -24,12 +27,23 @@ module('Unit | Adapter | application', function (hooks) { assert.expect(1); const adapter = this.owner.lookup('adapter:application'); // TODO this is icky, should be changed to a spy or stub + const originalAjax = RESTAdapter.prototype.ajax; RESTAdapter.prototype.ajax = (url, type) => { assert.equal(type, 'PATCH'); + RESTAdapter.prototype.ajax = originalAjax; }; adapter.ajax('/', 'PUT'); }); + test('it prenormalizes "empty" responses into a form that the fetch-manager will not reject', async function (assert) { + assert.expect(1); + const adapter = this.owner.lookup('adapter:project'); + const store = this.owner.lookup('service:store'); + this.server.get('/v1/projects', () => new Response({})); + const prenormalized = await adapter.findAll(store, {modelName: 'project'}, null, []); + assert.deepEqual(prenormalized, {items: []}); + }); + test('it correctly identifies 400 responses as invalid', function (assert) { assert.expect(2); const adapter = this.owner.lookup('adapter:application'); diff --git a/yarn.lock b/yarn.lock index 90f435da4..214eb95d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7330,6 +7330,21 @@ ember-cli-mirage@^1.1.6: lodash-es "^4.17.11" miragejs "^0.1.31" +ember-cli-mirage@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/ember-cli-mirage/-/ember-cli-mirage-1.1.7.tgz#846ec8e5199b15fa670da30dfa1708a8ee11e9dc" + integrity sha512-4kVLXXvgHYlv30ChM47U4xYnUtphIoEj+BQwXX0QT7pvD3KQxYnmfZSwlevSEVUy2UhbsBUPw6merENb+dIQ0A== + dependencies: + broccoli-file-creator "^2.1.1" + broccoli-funnel "^3.0.2" + broccoli-merge-trees "^4.2.0" + ember-auto-import "^1.2.19" + ember-cli-babel "^7.5.0" + ember-get-config "^0.2.2" + ember-inflector "^2.0.0 || ^3.0.0" + lodash-es "^4.17.11" + miragejs "^0.1.31" + ember-cli-normalize-entity-name@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ember-cli-normalize-entity-name/-/ember-cli-normalize-entity-name-1.0.0.tgz#0b14f7bcbc599aa117b5fddc81e4fd03c4bad5b7"