From 33d359e692d3c50e5122bea36b37aaf8b30d8d22 Mon Sep 17 00:00:00 2001 From: Zhihe Li Date: Thu, 11 Apr 2024 12:39:24 -0400 Subject: [PATCH] Fix tests from rebase (#2233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 🐛 Fix tests broken by rebase onto main --------- Co-authored-by: cameronperera --- .../api/addon/handlers/indexed-db-handler.js | 85 +++++++++++-------- addons/api/addon/serializers/role.js | 6 +- addons/api/addon/services/indexed-db.js | 55 +++++++----- .../tests/unit/utils/indexed-db-query-test.js | 9 +- .../scopes/scope/credential-stores/index.js | 4 +- .../controllers/scopes/scope/groups/index.js | 2 +- .../scopes/scope/groups/group/add-members.js | 2 - .../tests/acceptance/aliases/list-test.js | 2 + .../credential-store/create-test.js | 2 + 9 files changed, 103 insertions(+), 64 deletions(-) diff --git a/addons/api/addon/handlers/indexed-db-handler.js b/addons/api/addon/handlers/indexed-db-handler.js index 6d760568d..3c7c93fbd 100644 --- a/addons/api/addon/handlers/indexed-db-handler.js +++ b/addons/api/addon/handlers/indexed-db-handler.js @@ -35,59 +35,60 @@ export default class IndexedDbHandler { } let { page, pageSize, query: queryObj, ...remainingQuery } = query; - const tokenKey = `${type}-${hashCode(remainingQuery)}`; + + const adapter = store.adapterFor(type); + const schema = store.modelFor(type); + const serializer = store.serializerFor(type); if (!peekIndexedDB) { + const tokenKey = `${type}-${hashCode(remainingQuery)}`; const listToken = await indexedDb.token.get(tokenKey); - const adapter = store.adapterFor(type); - const schema = store.modelFor(type); let payload; try { payload = await adapter.query(store, schema, { ...remainingQuery, list_token: listToken?.token, - }); - } catch (e) { - // If we get an invalid list token, we'll delete the token and - // clear the DB and try again without a token. - if ( - e?.errors[0].status === 400 && - e?.errors[0].code === 'invalid list token' - ) { - await indexedDb[type].clear(); - // Delete all tokens of the same resource type so we don't keep clearing the DB. - // Even if other tokens may still be valid, we might have cleared data that they - // depended on so we should clear all tokens of the same type when clearing the DB. - const tokenKeys = await indexedDb.token - .where(':id') - .startsWith(type) - .primaryKeys(); - - await indexedDb.token.bulkDelete(tokenKeys); - - payload = await adapter.query(store, schema, { - ...remainingQuery, }); - } else { - throw e; + } catch (e) { + // If we get an invalid list token, we'll delete the token and + // clear the DB and try again without a token. + if ( + e?.errors[0].status === 400 && + e?.errors[0].code === 'invalid list token' + ) { + await indexedDb[type].clear(); + // Delete all tokens of the same resource type so we don't keep clearing the DB. + // Even if other tokens may still be valid, we might have cleared data that they + // depended on so we should clear all tokens of the same type when clearing the DB. + const tokenKeys = await indexedDb.token + .where(':id') + .startsWith(type) + .primaryKeys(); + + await indexedDb.token.bulkDelete(tokenKeys); + + payload = await adapter.query(store, schema, { + ...remainingQuery, + }); + } else { + throw e; + } } - } // Store the token we just got back from the payload if it exists - if (payload.list_token) { - await indexedDb.token.put({ - id: tokenKey, - token: payload.list_token, - }); - } + if (payload.list_token) { + await indexedDb.token.put({ + id: tokenKey, + token: payload.list_token, + }); + } // Remove any records from the DB if the API indicates they've been deleted if (payload.removed_ids?.length > 0) { await indexedDb[type].bulkDelete(payload.removed_ids); } - const serializer = store.serializerFor(type); const normalizedPayload = serializer.normalizeResponse( store, schema, @@ -100,8 +101,14 @@ export default class IndexedDbHandler { // Store the new data we just got back from the API refresh const items = payloadData.map((datum) => - this.indexedDb.normalizeData(datum, true), + this.indexedDb.normalizeData({ + data: datum, + cleanData: true, + schema, + serializer, + }), ); + await indexedDb[type].bulkPut(items); } @@ -112,7 +119,13 @@ export default class IndexedDbHandler { ); const dbRecords = paginateResults(indexedDbResults, page, pageSize).map( - (item) => this.indexedDb.normalizeData(item, false), + (item) => + this.indexedDb.normalizeData({ + data: item, + cleanData: false, + schema, + serializer, + }), ); // Return the raw data if we don't push to the store. diff --git a/addons/api/addon/serializers/role.js b/addons/api/addon/serializers/role.js index 4ac1fa3e4..5e6db0489 100644 --- a/addons/api/addon/serializers/role.js +++ b/addons/api/addon/serializers/role.js @@ -14,7 +14,7 @@ export default class RoleSerializer extends ApplicationSerializer { * include **only grants** and the version. Normally, grants are not * serialized. * If `adapterOptions.principalIDs` is set (to an array of user and - * group IDs), then the payload is serialized via `serializewithPrincipals`. + * group IDs), then the payload is serialized via `serializeWithPrincipals`. * @override * @param {Snapshot} snapshot * @return {object} @@ -26,7 +26,7 @@ export default class RoleSerializer extends ApplicationSerializer { serialized = this.serializeWithGrantStrings(snapshot, grantStrings); const principalIDs = snapshot?.adapterOptions?.principalIDs; if (principalIDs) - serialized = this.serializewithPrincipals(snapshot, principalIDs); + serialized = this.serializeWithPrincipals(snapshot, principalIDs); return serialized; } @@ -50,7 +50,7 @@ export default class RoleSerializer extends ApplicationSerializer { * @param {[string]} principalIDs * @return {object} */ - serializewithPrincipals(snapshot, principalIDs) { + serializeWithPrincipals(snapshot, principalIDs) { return { version: snapshot.attr('version'), principal_ids: principalIDs, diff --git a/addons/api/addon/services/indexed-db.js b/addons/api/addon/services/indexed-db.js index 34b1ddd0a..c0369d80a 100644 --- a/addons/api/addon/services/indexed-db.js +++ b/addons/api/addon/services/indexed-db.js @@ -28,17 +28,20 @@ export const formatDbName = (userId, clusterUrl) => `boundary-${userId}-${clusterUrl}`; /** - * Recursively copy over the object and convert any booleans to "true" or "false" so they can be indexed + * Recursively copy over the object and convert any booleans to "true" or "false" so they can be indexed. + * Also converts any ember-like arrays back to a standard array. * @param result * @param object * @returns {*} */ -const convertBooleansInObject = (result, object) => { +const convertFields = (result, object) => { Object.entries(object).forEach(([key, value]) => { if (typeOf(value) === 'object') { - result[key] = convertBooleansInObject({}, value); + result[key] = convertFields({}, value); } else if (typeOf(value) === 'boolean') { result[key] = value ? 'true' : 'false'; + } else if (typeOf(value) === 'array') { + result[key] = [...value]; } else { result[key] = value; } @@ -48,17 +51,31 @@ const convertBooleansInObject = (result, object) => { }; /** - * Recursively copy over the object and convert any string "true" or "false" back to booleans + * Recursively copy over the object and convert any string "true" or "false" back to booleans. + * Will also convert any array fields back using their model transformation. * @param result * @param object + * @param schema + * @param serializer * @returns {*} */ -const unconvertBooleansInObject = (result, object) => { +const unconvertFields = (result, object, schema, serializer) => { Object.entries(object).forEach(([key, value]) => { if (typeOf(value) === 'object') { - result[key] = unconvertBooleansInObject({}, value); + result[key] = unconvertFields({}, value, schema, serializer); } else if (value === 'true' || value === 'false') { result[key] = value === 'true'; + } else if (typeOf(value) === 'array') { + const attribute = schema.attributes.get(key); + + // If attribute type is array, we transform it back to a tracked array + if (attribute?.type === 'array') { + const transformer = serializer.transformFor(attribute.type); + result[key] = transformer.deserialize(value, attribute.options); + return; + } + + result[key] = value; } else { result[key] = value; } @@ -89,20 +106,20 @@ export default class IndexedDbService extends Service { } /** - * Cleanup the data by converting all booleans to "false" or "true". + * Cleanup the data by converting all fields to plain javascript objects. + * We will also convert booleans to "false" or "true". * IndexedDB doesn't support indexes on boolean values so we have to * convert them to be able to support using boolean values. * - * Data is assumed to be in POJOs and that our normalized data are not - * ember objects, otherwise they should also be converted. - * - * Undo the operation by setting `convertBooleans` to false. + * Undo the operation by setting `convertFields` to false. * * @param {Object} data - * @param {Boolean} convertBooleans + * @param {Boolean} cleanData + * @param {Object} schema + * @param {Object} serializer * @return {{id, type, attributes: {}, relationships: {}}} */ - normalizeData(data, convertBooleans) { + normalizeData({ data, cleanData, schema, serializer }) { if (!data) { return data; } @@ -112,12 +129,12 @@ export default class IndexedDbService extends Service { return { id, type, - attributes: convertBooleans - ? convertBooleansInObject({}, attributes) - : unconvertBooleansInObject({}, attributes), - relationships: convertBooleans - ? convertBooleansInObject({}, relationships) - : unconvertBooleansInObject({}, relationships), + attributes: cleanData + ? convertFields({}, attributes) + : unconvertFields({}, attributes, schema, serializer), + relationships: cleanData + ? convertFields({}, relationships) + : unconvertFields({}, relationships, schema, serializer), }; } } diff --git a/addons/api/tests/unit/utils/indexed-db-query-test.js b/addons/api/tests/unit/utils/indexed-db-query-test.js index ae2ad038b..e40e438a7 100644 --- a/addons/api/tests/unit/utils/indexed-db-query-test.js +++ b/addons/api/tests/unit/utils/indexed-db-query-test.js @@ -22,7 +22,14 @@ const seedIndexDb = async (resource, store, indexedDb, server) => { ); await indexedDb.db[resource].bulkPut( - normalizedPayload.data.map((datum) => indexedDb.normalizeData(datum, true)), + normalizedPayload.data.map((datum) => + indexedDb.normalizeData({ + data: datum, + cleanData: true, + schema, + serializer, + }), + ), ); }; diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/index.js b/ui/admin/app/controllers/scopes/scope/credential-stores/index.js index c55f82f7b..8eed270ef 100644 --- a/ui/admin/app/controllers/scopes/scope/credential-stores/index.js +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/index.js @@ -75,7 +75,7 @@ export default class ScopesScopeCredentialStoresIndexController extends Controll const { isNew } = credentialStore; credentialStore.rollbackAttributes(); if (isNew) { - await this.router.transitionTo('scopes.scope.credential-stores'); + this.router.transitionTo('scopes.scope.credential-stores'); } } @@ -90,7 +90,7 @@ export default class ScopesScopeCredentialStoresIndexController extends Controll @notifySuccess('notifications.delete-success') async delete(credentialStore) { await credentialStore.destroyRecord(); - await this.router.replaceWith('scopes.scope.credential-stores'); + this.router.replaceWith('scopes.scope.credential-stores'); await this.router.refresh(); } diff --git a/ui/admin/app/controllers/scopes/scope/groups/index.js b/ui/admin/app/controllers/scopes/scope/groups/index.js index 6c52fb7ad..c642fa0c9 100644 --- a/ui/admin/app/controllers/scopes/scope/groups/index.js +++ b/ui/admin/app/controllers/scopes/scope/groups/index.js @@ -35,7 +35,7 @@ export default class ScopesScopeGroupsIndexController extends Controller { async cancel(group) { const { isNew } = group; group.rollbackAttributes(); - if (isNew) await this.router.transitionTo('scopes.scope.groups'); + if (isNew) this.router.transitionTo('scopes.scope.groups'); } /** diff --git a/ui/admin/app/routes/scopes/scope/groups/group/add-members.js b/ui/admin/app/routes/scopes/scope/groups/group/add-members.js index 82c67766b..3d5a5a881 100644 --- a/ui/admin/app/routes/scopes/scope/groups/group/add-members.js +++ b/ui/admin/app/routes/scopes/scope/groups/group/add-members.js @@ -6,8 +6,6 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { action } from '@ember/object'; -import { loading } from 'ember-loading'; -import { notifySuccess, notifyError } from 'core/decorators/notify'; import { resourceFilter } from 'core/decorators/resource-filter'; export default class ScopesScopeGroupsGroupAddMembersRoute extends Route { diff --git a/ui/admin/tests/acceptance/aliases/list-test.js b/ui/admin/tests/acceptance/aliases/list-test.js index 6c55e98ac..0dcfaaac0 100644 --- a/ui/admin/tests/acceptance/aliases/list-test.js +++ b/ui/admin/tests/acceptance/aliases/list-test.js @@ -7,11 +7,13 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | aliases | list', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupIndexedDb(hooks); let intl; diff --git a/ui/admin/tests/acceptance/credential-store/create-test.js b/ui/admin/tests/acceptance/credential-store/create-test.js index 5fe0d7eb8..3d4dba34c 100644 --- a/ui/admin/tests/acceptance/credential-store/create-test.js +++ b/ui/admin/tests/acceptance/credential-store/create-test.js @@ -7,12 +7,14 @@ import { module, test } from 'qunit'; import { visit, currentURL, find, click, fillIn } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { Response } from 'miragejs'; module('Acceptance | credential-stores | create', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupIndexedDb(hooks); let featuresService; let getCredentialStoresCount;