diff --git a/addons/api/addon/models/fragment-host-set.js b/addons/api/addon/models/fragment-host-set.js new file mode 100644 index 000000000..975d7e1f0 --- /dev/null +++ b/addons/api/addon/models/fragment-host-set.js @@ -0,0 +1,16 @@ +import Fragment from 'ember-data-model-fragments/fragment'; +import { attr } from '@ember-data/model'; + +export default class FragmentHostSetModel extends Fragment { + + // =attributes + + @attr('string', { + description: 'ID of the related host set' + }) host_set_id; + + @attr('string', { + description: 'ID of the catalog to which the host set belongs' + }) host_catalog_id; + +} diff --git a/addons/api/addon/models/target.js b/addons/api/addon/models/target.js index 84b2658dc..66cd0fdfa 100644 --- a/addons/api/addon/models/target.js +++ b/addons/api/addon/models/target.js @@ -1,3 +1,39 @@ import GeneratedTargetModel from '../generated/models/target'; +import { fragmentArray } from 'ember-data-model-fragments/attributes'; -export default class TargetModel extends GeneratedTargetModel {} +export default class TargetModel extends GeneratedTargetModel { + + // =attributes + + @fragmentArray('fragment-host-set', {readOnly: true}) host_sets; + + // =methods + + /** + * Saves the `host_set_ids` array on the via the `set-hosts` method. + * See serializer and adapter for more information. + * @param {object} options + * @param {object} options.adapterOptions + * @return {Promise} + */ + saveHostSets(options={ adapterOptions: {} }) { + const defaultOptions = { + adapterOptions: { + method: 'set-host-sets', + serializeHostSets: true + } + }; + // There is no "deep merge" in ES. + // All of this nonsense is here to ensure we get + // a decent merge of `adapterOptions`. + return this.save({ + ...defaultOptions, + ...options, + adapterOptions: { + ...defaultOptions.adapterOptions, + ...options.adapterOptions + } + }); + } + +} diff --git a/addons/api/addon/serializers/target.js b/addons/api/addon/serializers/target.js new file mode 100644 index 000000000..fefaf0fcb --- /dev/null +++ b/addons/api/addon/serializers/target.js @@ -0,0 +1,35 @@ +import ApplicationSerializer from './application'; + +export default class TargetSerializer extends ApplicationSerializer { + + // =methods + + /** + * If `adapterOptions.serializeHostSets` is true, the serialization should + * include **only host sets** and the version. Normally, host sets are not + * serialized. + * @override + * @param {Snapshot} snapshot + * @return {object} + */ + serialize(snapshot) { + const serializeHostSets = snapshot?.adapterOptions?.serializeHostSets; + let serialized = super.serialize(...arguments); + if (serializeHostSets) serialized = this.serializeWithHostSets(snapshot); + return serialized; + } + + /** + * Returns a payload containing only the host_set_ids array. + * @param {Snapshot} snapshot + * @return {object} + */ + serializeWithHostSets(snapshot) { + return { + version: snapshot.attr('version'), + host_set_ids: + snapshot.attr('host_sets').map(hostSet => hostSet.attr('host_set_id')) + }; + } + +} diff --git a/addons/api/app/models/fragment-host-set.js b/addons/api/app/models/fragment-host-set.js new file mode 100644 index 000000000..bd1726680 --- /dev/null +++ b/addons/api/app/models/fragment-host-set.js @@ -0,0 +1 @@ +export { default } from 'api/models/fragment-host-set'; diff --git a/addons/api/app/serializers/target.js b/addons/api/app/serializers/target.js new file mode 100644 index 000000000..6b8e5abe4 --- /dev/null +++ b/addons/api/app/serializers/target.js @@ -0,0 +1 @@ +export { default } from 'api/serializers/target'; diff --git a/addons/api/tests/unit/models/fragment-host-set-test.js b/addons/api/tests/unit/models/fragment-host-set-test.js new file mode 100644 index 000000000..5862a94a3 --- /dev/null +++ b/addons/api/tests/unit/models/fragment-host-set-test.js @@ -0,0 +1,13 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Model | fragment host set', function(hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function(assert) { + let store = this.owner.lookup('service:store'); + let model = store.createRecord('fragment-host-set', {}); + assert.ok(model); + }); +}); diff --git a/addons/api/tests/unit/models/target-test.js b/addons/api/tests/unit/models/target-test.js index 78fc71e43..a9ae5ca15 100644 --- a/addons/api/tests/unit/models/target-test.js +++ b/addons/api/tests/unit/models/target-test.js @@ -1,13 +1,47 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; -module('Unit | Model | target', function(hooks) { +module('Unit | Model | target', function (hooks) { setupTest(hooks); + setupMirage(hooks); - // Replace this with your real tests. test('it exists', function(assert) { let store = this.owner.lookup('service:store'); let model = store.createRecord('target', {}); assert.ok(model); }); + + test('it has a `saveHostSets` method that targets a specific POST API endpoint and serialization', async function (assert) { + assert.expect(1); + this.server.post('/v1/scopes/o_1/targets/123abc:set-host-sets', (schema, request) => { + const body = JSON.parse(request.requestBody); + assert.deepEqual(body, { + host_set_ids: ['1', '3'], + version: 1 + }); + }); + const store = this.owner.lookup('service:store'); + store.push({ + data: { + id: '123abc', + type: 'target', + attributes: { + name: 'Target', + description: 'Description', + host_sets: [ + { host_set_id: '1', host_catalog_id: '2' }, + { host_set_id: '3', host_catalog_id: '4' } + ], + version: 1, + scope: { + scope_id: 'o_1', + type: 'scope' + } + } + } + }); + const model = store.peekRecord('target', '123abc'); + await model.saveHostSets(); + }); }); diff --git a/addons/api/tests/unit/serializers/target-test.js b/addons/api/tests/unit/serializers/target-test.js new file mode 100644 index 000000000..a3ca9bd69 --- /dev/null +++ b/addons/api/tests/unit/serializers/target-test.js @@ -0,0 +1,91 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Serializer | target', function(hooks) { + setupTest(hooks); + + test('it serializes targets normally, without host sets', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('target'); + const record = store.createRecord('target', { + name: 'User', + description: 'Description', + host_sets: [ + { host_set_id: '1', host_catalog_id: '2' }, + { host_set_id: '3', host_catalog_id: '4' } + ], + version: 1 + }); + const snapshot = record._createSnapshot(); + snapshot.adapterOptions = {}; + const serializedRecord = serializer.serialize(snapshot); + assert.deepEqual(serializedRecord, { + name: 'User', + description: 'Description', + version: 1, + protocol: null + }); + + }); + + test('it serializes only host sets when `adapterOptions.serializeHostSets` is true', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('target'); + const record = store.createRecord('target', { + name: 'User', + description: 'Description', + host_sets: [ + { host_set_id: '1', host_catalog_id: '2' }, + { host_set_id: '3', host_catalog_id: '4' } + ], + version: 1 + }); + const snapshot = record._createSnapshot(); + snapshot.adapterOptions = { + serializeHostSets: true + }; + const serializedRecord = serializer.serialize(snapshot); + assert.deepEqual(serializedRecord, { + host_set_ids: ['1', '3'], + version: 1 + }); + }); + + test('it normalizes records with array fields', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('target'); + const targetModelClass = store.createRecord('target').constructor; + const payload = { + id: '1', + name: 'Target 1', + host_sets: [ + { host_set_id: '1', host_catalog_id: '2' }, + { host_set_id: '3', host_catalog_id: '4' } + ] + }; + const normalized = serializer.normalizeSingleResponse( + store, + targetModelClass, + payload + ); + assert.deepEqual(normalized, { + included: [], + data: { + id: '1', + type: 'target', + attributes: { + name: 'Target 1', + host_sets: [ + { host_set_id: '1', host_catalog_id: '2' }, + { host_set_id: '3', host_catalog_id: '4' } + ] + }, + relationships: {}, + }, + }); + }); + +});