diff --git a/addons/api/addon/adapters/application.js b/addons/api/addon/adapters/application.js index 09b772cb7..187c94ad1 100644 --- a/addons/api/addon/adapters/application.js +++ b/addons/api/addon/adapters/application.js @@ -1,4 +1,5 @@ import RESTAdapter from '@ember-data/adapter/rest'; +import { serializeIntoHash } from '@ember-data/adapter/-private'; import AdapterBuildURLMixin from '../mixins/adapter-build-url'; import config from 'ember-get-config'; import { get, getWithDefault } from '@ember/object'; @@ -143,6 +144,26 @@ export default class ApplicationAdapter extends RESTAdapter.extend( return super.query(...arguments).then(prenormalizeArrayResponse); } + /** + * Our API expects update calls with a custom method suffix + * (e.g. `:set-grants`) to be made with POST instead of PUT/PATCH. + * If this method is called with a custom method via `adapterOptions.method`, + * the API request will be made with POST. All other update requests are + * made as normal. + * @override + * @param {Store} store + * @param {Model} type + * @param {Snapshot} snapshot + * @return {Promise} promise + */ + updateRecord(store, type, snapshot) { + const method = get(snapshot, 'adapterOptions.method'); + const data = serializeIntoHash(store, type, snapshot, {}); + const id = snapshot.id; + const url = this.buildURL(type.modelName, id, snapshot, 'updateRecord'); + return this.ajax(url, method ? 'POST' : 'PUT', { data }); + } + /** * 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/addon/serializers/role.js b/addons/api/addon/serializers/role.js index 69e8fe568..d22f01ef0 100644 --- a/addons/api/addon/serializers/role.js +++ b/addons/api/addon/serializers/role.js @@ -20,6 +20,11 @@ export default class RoleSerializer extends ApplicationSerializer { return serialized; } + /** + * Returns a payload containing only the grants array. + * @param {Snapshot} snapshot + * @return {object} + */ serializeWithGrants(snapshot) { return { grants: snapshot.attr('grants') diff --git a/addons/api/tests/unit/adapters/application-test.js b/addons/api/tests/unit/adapters/application-test.js index c3d72d469..dc984bba2 100644 --- a/addons/api/tests/unit/adapters/application-test.js +++ b/addons/api/tests/unit/adapters/application-test.js @@ -112,6 +112,23 @@ module('Unit | Adapter | application', function (hooks) { adapter.ajax('/', 'PUT'); }); + test('it rewrites PUT/PATCH to POST when `adapterOptions.method` is passed to `updateRecord`', function (assert) { + assert.expect(1); + const adapter = this.owner.lookup('adapter:application'); + const store = this.owner.lookup('service:store'); + const snapshot = store.createRecord('role', {})._createSnapshot(); + snapshot.adapterOptions = { + method: 'custom-method' + } + // 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, 'POST'); + RESTAdapter.prototype.ajax = originalAjax; + }; + adapter.updateRecord(store, {modelName: 'user'}, snapshot); + }); + 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:application'); diff --git a/addons/api/tests/unit/models/role-test.js b/addons/api/tests/unit/models/role-test.js index b47f31343..68160dec1 100644 --- a/addons/api/tests/unit/models/role-test.js +++ b/addons/api/tests/unit/models/role-test.js @@ -1,12 +1,42 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; module('Unit | Model | role', function (hooks) { setupTest(hooks); + setupMirage(hooks); test('it exists', function(assert) { let store = this.owner.lookup('service:store'); let model = store.createRecord('role', {}); assert.ok(model); }); + + test('it has a `saveGrants` method that targets a specific POST API endpoint and serialization', async function (assert) { + assert.expect(1); + this.server.post('/v1/scopes/o_1/roles/123abc:set-grants', (schema, request) => { + const body = JSON.parse(request.requestBody); + assert.deepEqual(body, { + grants: ['grant1', 'grant2'] + }); + }); + const store = this.owner.lookup('service:store'); + store.push({ + data: { + id: '123abc', + type: 'role', + attributes: { + name: 'Role', + description: 'Description', + grants: ['grant1', 'grant2'], + scope: { + scope_id: 'o_1', + type: 'scope' + } + } + } + }); + const model = store.peekRecord('role', '123abc'); + await model.saveGrants(); + }); });