Transit flaky test revisit (#25563)

* here we go...

* glimmerize model to help —maybe—with capabilities checks

* remove waitUntils
This commit is contained in:
Angel Garbarino
2024-02-21 14:18:12 -07:00
committed by GitHub
parent ef4adca32c
commit d1885ee558
3 changed files with 88 additions and 85 deletions

View File

@@ -6,14 +6,18 @@
import Component from '@glimmer/component'; import Component from '@glimmer/component';
import { service } from '@ember/service'; import { service } from '@ember/service';
import { action } from '@ember/object'; import { action } from '@ember/object';
import { buildWaiter } from '@ember/test-waiters';
import errorMessage from 'vault/utils/error-message'; import errorMessage from 'vault/utils/error-message';
const waiter = buildWaiter('transit-form-show');
export default class TransitFormShow extends Component { export default class TransitFormShow extends Component {
@service store; @service store;
@service router; @service router;
@service flashMessages; @service flashMessages;
@action async rotateKey() { @action async rotateKey() {
const waiterToken = waiter.beginAsync();
const { backend, id } = this.args.key; const { backend, id } = this.args.key;
try { try {
await this.store.adapterFor('transit-key').keyAction('rotate', { backend, id }); await this.store.adapterFor('transit-key').keyAction('rotate', { backend, id });
@@ -22,6 +26,8 @@ export default class TransitFormShow extends Component {
await this.router.refresh(); await this.router.refresh();
} catch (e) { } catch (e) {
this.flashMessages.danger(errorMessage(e)); this.flashMessages.danger(errorMessage(e));
} finally {
waiter.endAsync(waiterToken);
} }
} }
} }

View File

@@ -4,102 +4,113 @@
*/ */
import Model, { attr } from '@ember-data/model'; import Model, { attr } from '@ember-data/model';
import { alias } from '@ember/object/computed'; import { set, get } from '@ember/object';
import { set, get, computed } from '@ember/object';
import clamp from 'vault/utils/clamp'; import clamp from 'vault/utils/clamp';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities'; import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
const ACTION_VALUES = { const ACTION_VALUES = {
encrypt: { encrypt: {
isSupported: 'supportsEncryption', isSupported: 'supportsEncryption',
description: 'Looks up wrapping properties for the given token', description: 'Looks up wrapping properties for the given token.',
glyph: 'lock-fill', glyph: 'lock-fill',
}, },
decrypt: { decrypt: {
isSupported: 'supportsDecryption', isSupported: 'supportsDecryption',
description: 'Decrypts the provided ciphertext using this key', description: 'Decrypts the provided ciphertext using this key.',
glyph: 'mail-open', glyph: 'mail-open',
}, },
datakey: { datakey: {
isSupported: 'supportsEncryption', isSupported: 'supportsEncryption',
description: 'Generates a new key and value encrypted with this key', description: 'Generates a new key and value encrypted with this key.',
glyph: 'key', glyph: 'key',
}, },
rewrap: { rewrap: {
isSupported: 'supportsEncryption', isSupported: 'supportsEncryption',
description: 'Rewraps the ciphertext using the latest version of the named key', description: 'Rewraps the ciphertext using the latest version of the named key.',
glyph: 'reload', glyph: 'reload',
}, },
sign: { sign: {
isSupported: 'supportsSigning', isSupported: 'supportsSigning',
description: 'Get the cryptographic signature of the given data', description: 'Get the cryptographic signature of the given data.',
glyph: 'pencil-tool', glyph: 'pencil-tool',
}, },
hmac: { hmac: {
isSupported: true, isSupported: true,
description: 'Generate a data digest using a hash algorithm', description: 'Generate a data digest using a hash algorithm.',
glyph: 'shuffle', glyph: 'shuffle',
}, },
verify: { verify: {
isSupported: true, isSupported: true,
description: 'Validate the provided signature for the given data', description: 'Validate the provided signature for the given data.',
glyph: 'check-circle', glyph: 'check-circle',
}, },
export: { export: {
isSupported: 'exportable', isSupported: 'exportable',
description: 'Get the named key', description: 'Get the named key.',
glyph: 'external-link', glyph: 'external-link',
}, },
}; };
export default Model.extend({ export default class TransitKeyModel extends Model {
type: attr('string', { @attr('string') backend;
@attr('string', {
defaultValue: 'aes256-gcm96', defaultValue: 'aes256-gcm96',
}), })
name: attr('string', { type;
@attr('string', {
label: 'Name', label: 'Name',
readOnly: true, readOnly: true,
}), })
autoRotatePeriod: attr({ name;
@attr({
defaultValue: '0', defaultValue: '0',
defaultShown: 'Key is not automatically rotated', defaultShown: 'Key is not automatically rotated',
editType: 'ttl', editType: 'ttl',
label: 'Auto-rotation period', label: 'Auto-rotation period',
}), })
deletionAllowed: attr('boolean'), autoRotatePeriod;
derived: attr('boolean'),
exportable: attr('boolean'),
minDecryptionVersion: attr('number', {
defaultValue: 1,
}),
minEncryptionVersion: attr('number', {
defaultValue: 0,
}),
latestVersion: attr('number'),
keys: attr('object'),
convergentEncryption: attr('boolean'),
convergentEncryptionVersion: attr('number'),
supportsSigning: attr('boolean'), @attr('boolean') deletionAllowed;
supportsEncryption: attr('boolean'), @attr('boolean') derived;
supportsDecryption: attr('boolean'), @attr('boolean') exportable;
supportsDerivation: attr('boolean'),
@attr('number', {
defaultValue: 1,
})
minDecryptionVersion;
@attr('number', {
defaultValue: 0,
})
minEncryptionVersion;
@attr('number') latestVersion;
@attr('object') keys;
@attr('boolean') convergentEncryption;
@attr('number') convergentEncryptionVersion;
@attr('boolean') supportsSigning;
@attr('boolean') supportsEncryption;
@attr('boolean') supportsDecryption;
@attr('boolean') supportsDerivation;
setConvergentEncryption(val) { setConvergentEncryption(val) {
if (val === true) { if (val === true) {
set(this, 'derived', val); set(this, 'derived', val);
} }
set(this, 'convergentEncryption', val); set(this, 'convergentEncryption', val);
}, }
setDerived(val) { setDerived(val) {
if (val === false) { if (val === false) {
set(this, 'convergentEncryption', val); set(this, 'convergentEncryption', val);
} }
set(this, 'derived', val); set(this, 'derived', val);
}, }
supportedActions: computed('type', function () { get supportedActions() {
return Object.keys(ACTION_VALUES) return Object.keys(ACTION_VALUES)
.filter((name) => { .filter((name) => {
const { isSupported } = ACTION_VALUES[name]; const { isSupported } = ACTION_VALUES[name];
@@ -109,14 +120,14 @@ export default Model.extend({
const { description, glyph } = ACTION_VALUES[name]; const { description, glyph } = ACTION_VALUES[name];
return { name, description, glyph }; return { name, description, glyph };
}); });
}), }
canDelete: computed('deletionAllowed', 'lastLoadTS', function () { get canDelete() {
const deleteAttrChanged = Boolean(this.changedAttributes().deletionAllowed); const deleteAttrChanged = Boolean(this.changedAttributes().deletionAllowed);
return this.deletionAllowed && deleteAttrChanged === false; return this.deletionAllowed && deleteAttrChanged === false;
}), }
keyVersions: computed('validKeyVersions', function () { get keyVersions() {
let maxVersion = Math.max(...this.validKeyVersions); let maxVersion = Math.max(...this.validKeyVersions);
const versions = []; const versions = [];
while (maxVersion > 0) { while (maxVersion > 0) {
@@ -124,14 +135,9 @@ export default Model.extend({
maxVersion--; maxVersion--;
} }
return versions; return versions;
}), }
encryptionKeyVersions: computed( get encryptionKeyVersions() {
'keyVerisons',
'keyVersions',
'latestVersion',
'minDecryptionVersion',
function () {
const { keyVersions, minDecryptionVersion } = this; const { keyVersions, minDecryptionVersion } = this;
return keyVersions return keyVersions
@@ -140,9 +146,8 @@ export default Model.extend({
}) })
.reverse(); .reverse();
} }
),
keysForEncryption: computed('minEncryptionVersion', 'latestVersion', function () { get keysForEncryption() {
let { minEncryptionVersion, latestVersion } = this; let { minEncryptionVersion, latestVersion } = this;
const minVersion = clamp(minEncryptionVersion - 1, 0, latestVersion); const minVersion = clamp(minEncryptionVersion - 1, 0, latestVersion);
const versions = []; const versions = [];
@@ -151,13 +156,13 @@ export default Model.extend({
latestVersion--; latestVersion--;
} }
return versions; return versions;
}), }
validKeyVersions: computed('keys', function () { get validKeyVersions() {
return Object.keys(this.keys); return Object.keys(this.keys);
}), }
exportKeyTypes: computed('exportable', 'supportsEncryption', 'supportsSigning', 'type', function () { get exportKeyTypes() {
const types = ['hmac']; const types = ['hmac'];
if (this.supportsSigning) { if (this.supportsSigning) {
types.unshift('signing'); types.unshift('signing');
@@ -166,13 +171,17 @@ export default Model.extend({
types.unshift('encryption'); types.unshift('encryption');
} }
return types; return types;
}), }
@lazyCapabilities(apiPath`${'backend'}/keys/${'id'}/rotate`, 'backend', 'id') rotatePath;
@lazyCapabilities(apiPath`${'backend'}/keys/${'id'}`, 'backend', 'id') secretPath;
backend: attr('string'), get canRotate() {
return this.rotatePath.get('canUpdate') !== false;
rotatePath: lazyCapabilities(apiPath`${'backend'}/keys/${'id'}/rotate`, 'backend', 'id'), }
canRotate: alias('rotatePath.canUpdate'), get canRead() {
secretPath: lazyCapabilities(apiPath`${'backend'}/keys/${'id'}`, 'backend', 'id'), return this.secretPath.get('canUpdate') !== false;
canRead: alias('secretPath.canUpdate'), }
canEdit: alias('secretPath.canUpdate'), get canEdit() {
}); return this.secretPath.get('canUpdate') !== false;
}
}

View File

@@ -3,7 +3,7 @@
* SPDX-License-Identifier: BUSL-1.1 * SPDX-License-Identifier: BUSL-1.1
*/ */
import { click, fillIn, find, currentURL, settled, visit, waitUntil, findAll } from '@ember/test-helpers'; import { click, fillIn, find, currentURL, settled, visit, findAll } from '@ember/test-helpers';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
@@ -345,17 +345,14 @@ module('Acceptance | transit (flaky)', function (hooks) {
await click(SELECTORS.versionsTab); await click(SELECTORS.versionsTab);
assert.dom(SELECTORS.versionRow(1)).hasTextContaining('Version 1', `${name}: only one key version`); assert.dom(SELECTORS.versionRow(1)).hasTextContaining('Version 1', `${name}: only one key version`);
await waitUntil(() => find(SELECTORS.rotate.trigger));
await click(SELECTORS.rotate.trigger); await click(SELECTORS.rotate.trigger);
await click(SELECTORS.rotate.confirm); await click(SELECTORS.rotate.confirm);
// wait for rotate call
await waitUntil(() => find(SELECTORS.versionRow(2)));
assert.dom(SELECTORS.versionRow(2)).exists('two key versions after rotate'); assert.dom(SELECTORS.versionRow(2)).exists('two key versions after rotate');
// navigate back to actions tab // navigate back to actions tab
await click(SELECTORS.actionsTab); await click(SELECTORS.actionsTab);
await waitUntil(() => find(SELECTORS.card('encrypt')));
assert.dom(SELECTORS.card('encrypt')).exists(`renders encrypt action card for ${name}`); assert.dom(SELECTORS.card('encrypt')).exists(`renders encrypt action card for ${name}`);
await click(SELECTORS.card('encrypt')); await click(SELECTORS.card('encrypt'));
assert assert
@@ -367,11 +364,6 @@ module('Acceptance | transit (flaky)', function (hooks) {
await testConvergentEncryption(assert, name); await testConvergentEncryption(assert, name);
}); });
/*
OLD FLAKY TESTS (skipped)
It's been a while since we've updated the transit engine
keeping these tests to run locally the next time we touch that secret engine
*/
const KEY_TYPE_COMBINATIONS = [ const KEY_TYPE_COMBINATIONS = [
{ {
name: (uid) => `aes-${uid}`, name: (uid) => `aes-${uid}`,
@@ -459,7 +451,7 @@ module('Acceptance | transit (flaky)', function (hooks) {
]; ];
for (const key of KEY_TYPE_COMBINATIONS) { for (const key of KEY_TYPE_COMBINATIONS) {
test.skip(`transit backend: ${key.type}`, async function (assert) { test(`transit backend: ${key.type}`, async function (assert) {
assert.expect(key.convergent ? 43 : 7); assert.expect(key.convergent ? 43 : 7);
const name = await this.generateTransitKey(key); const name = await this.generateTransitKey(key);
await visit(`vault/secrets/${this.path}/show/${name}`); await visit(`vault/secrets/${this.path}/show/${name}`);
@@ -473,12 +465,9 @@ module('Acceptance | transit (flaky)', function (hooks) {
// wait for capabilities // wait for capabilities
assert.dom('[data-test-transit-version]').exists({ count: 1 }, `${name}: only one key version`); assert.dom('[data-test-transit-version]').exists({ count: 1 }, `${name}: only one key version`);
await waitUntil(() => find(SELECTORS.rotate.trigger));
await click(SELECTORS.rotate.trigger); await click(SELECTORS.rotate.trigger);
await click(SELECTORS.rotate.confirm); await click(SELECTORS.rotate.confirm);
// wait for rotate call
await waitUntil(() => findAll('[data-test-transit-version]').length >= 2);
assert assert
.dom('[data-test-transit-version]') .dom('[data-test-transit-version]')
.exists({ count: 2 }, `${name}: two key versions after rotate`); .exists({ count: 2 }, `${name}: two key versions after rotate`);
@@ -491,7 +480,6 @@ module('Acceptance | transit (flaky)', function (hooks) {
); );
const keyAction = key.supportsEncryption ? 'encrypt' : 'sign'; const keyAction = key.supportsEncryption ? 'encrypt' : 'sign';
await waitUntil(() => find(`[data-test-transit-action-title=${keyAction}]`));
assert assert
.dom(`[data-test-transit-action-title=${keyAction}]`) .dom(`[data-test-transit-action-title=${keyAction}]`)