mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-18 18:38:08 -05:00
UI Bug fix: Fix DR Secondary view (#31478)
* test coverage and the fix * not working * fix failing test * fix another test * changelog * the correct changelog number
This commit is contained in:
parent
b76a28a1e0
commit
b0d8f4f1be
11 changed files with 355 additions and 142 deletions
3
changelog/31478.txt
Normal file
3
changelog/31478.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:bug
|
||||
ui: Fix DR secondary view from not loading/transitioning.
|
||||
```
|
||||
|
|
@ -110,8 +110,10 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
|
|||
|
||||
poll: task(function* () {
|
||||
while (true) {
|
||||
// when testing, the polling loop causes promises to never settle so acceptance tests hang
|
||||
// to get around that, we just disable the poll in tests
|
||||
// In test mode, polling causes acceptance tests to hang due to never-settling promises.
|
||||
// To avoid this, polling is disabled during tests.
|
||||
// If your test depends on cluster status changes (e.g., replication mode),
|
||||
// manually trigger polling using pollCluster from 'vault/tests/helpers/poll-cluster'.
|
||||
if (Ember.testing) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -128,7 +130,8 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
|
|||
.cancelOn('deactivate')
|
||||
.keepLatest(),
|
||||
|
||||
async afterModel(model, transition) {
|
||||
// Note: do not make this afterModel hook async, it will break the DR secondary flow.
|
||||
afterModel(model, transition) {
|
||||
this._super(...arguments);
|
||||
|
||||
this.currentCluster.setCluster(model);
|
||||
|
|
@ -142,7 +145,20 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
|
|||
if (this.namespaceService.path && !this.version.hasNamespaces) {
|
||||
return this.router.transitionTo(this.routeName, { queryParams: { namespace: '' } });
|
||||
}
|
||||
// Skip analytics initialization if the cluster is a DR secondary:
|
||||
// 1. There is little value in collecting analytics in this state.
|
||||
// 2. The analytics service requires resolving async setup (e.g. await),
|
||||
// which delays the afterModel hook resolution and breaks the DR secondary flow.
|
||||
if (model.dr?.isSecondary) {
|
||||
return this.transitionToTargetRoute(transition);
|
||||
}
|
||||
|
||||
this.addAnalyticsService(model);
|
||||
|
||||
return this.transitionToTargetRoute(transition);
|
||||
},
|
||||
|
||||
async addAnalyticsService(model) {
|
||||
// identify user for analytics service
|
||||
if (this.analytics.activated) {
|
||||
let licenseId = '';
|
||||
|
|
@ -172,8 +188,6 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
|
|||
console.log('unable to start analytics', e);
|
||||
}
|
||||
}
|
||||
|
||||
return this.transitionToTargetRoute(transition);
|
||||
},
|
||||
|
||||
setupController() {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
<h3 class="title is-5 is-marginless">
|
||||
Promote cluster
|
||||
</h3>
|
||||
<p class="has-top-padding-s">
|
||||
<p class="has-top-padding-s" data-test-promote-description>
|
||||
Promote this cluster to a
|
||||
{{this.model.replicationModeForDisplay}}
|
||||
primary
|
||||
|
|
|
|||
|
|
@ -37,12 +37,16 @@
|
|||
{{else}}
|
||||
<ul>
|
||||
<li>
|
||||
<LinkTo @route="vault.cluster.replication-dr-promote.details">
|
||||
<LinkTo @route="vault.cluster.replication-dr-promote.details" data-test-link-to="Details">
|
||||
Details
|
||||
</LinkTo>
|
||||
</li>
|
||||
<li>
|
||||
<LinkTo @route="vault.cluster.replication-dr-promote" @current-when="vault.cluster.replication-dr-promote.index">
|
||||
<LinkTo
|
||||
@route="vault.cluster.replication-dr-promote"
|
||||
@current-when="vault.cluster.replication-dr-promote.index"
|
||||
data-test-link-to="Manage"
|
||||
>
|
||||
Manage
|
||||
</LinkTo>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -29,7 +29,12 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
{{#if this.cluster.canAddSecondary}}
|
||||
<LinkTo @route="mode.secondaries.add" @model={{this.cluster.replicationMode}} class="link add-secondaries">
|
||||
<LinkTo
|
||||
@route="mode.secondaries.add"
|
||||
@model={{this.cluster.replicationMode}}
|
||||
class="link add-secondaries"
|
||||
data-test-link-to="Add secondary"
|
||||
>
|
||||
Add secondary
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
{{/if}}
|
||||
<hr class="has-background-gray-100" />
|
||||
<Hds::ButtonSet>
|
||||
<Hds::Button @text="Generate token" type="submit" data-test-secondary-add />
|
||||
<Hds::Button @text="Generate token" type="submit" data-test-submit />
|
||||
<Hds::Button @text="Cancel" @color="secondary" @route="mode.secondaries" @model={{this.model.replicationMode}} />
|
||||
</Hds::ButtonSet>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
* Copyright (c) HashiCorp, Inc.
|
||||
* SPDX-License-Identifier: BUSL-1.1
|
||||
*/
|
||||
|
||||
import { click, visit, settled } from '@ember/test-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import { login, logout } from 'vault/tests/helpers/auth/auth-helpers';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import sinon from 'sinon';
|
||||
import { disableReplication, enableReplication } from 'vault/tests/helpers/replication';
|
||||
import { pollCluster } from 'vault/tests/helpers/poll-cluster';
|
||||
|
||||
// To allow a user to login and create a secondary dr cluster we demote a primary dr cluster.
|
||||
// We stub this demotion so we do not break the dev process for all future tests.
|
||||
// All DR secondary assertions are done in one test to avoid the lengthy setup and teardown process.
|
||||
module('Acceptance | Enterprise | replication-secondaries', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
await login();
|
||||
await settled();
|
||||
await disableReplication('dr');
|
||||
await settled();
|
||||
await disableReplication('performance');
|
||||
await settled();
|
||||
});
|
||||
|
||||
hooks.afterEach(async function () {
|
||||
// For the tests following this, return to a good state.
|
||||
// We've reset mirage with this.server.shutdown() but re-poll the cluster to get the latest state.
|
||||
this.server.shutdown();
|
||||
await pollCluster(this.owner);
|
||||
await disableReplication('dr');
|
||||
await settled();
|
||||
await pollCluster(this.owner);
|
||||
await logout();
|
||||
});
|
||||
|
||||
test('DR secondary: manage tab, details tab, and analytics are not run', async function (assert) {
|
||||
// Log in and set up a DR primary
|
||||
await login();
|
||||
await settled();
|
||||
await enableReplication('dr', 'primary');
|
||||
await pollCluster(this.owner);
|
||||
await click('[data-test-replication-link="manage"]');
|
||||
|
||||
// Stub the demote action so it does not actually demote the cluster
|
||||
this.server.post('/sys/replication/dr/demote', () => {
|
||||
return { request_id: 'fake-demote', data: { success: true } };
|
||||
});
|
||||
// Stub endpoints for DR secondary state
|
||||
this.server.post('/sys/capabilities-self', () => ({ capabilities: [] }));
|
||||
this.server.get('/sys/replication/status', () => ({
|
||||
request_id: '2f50313f-be70-493d-5883-c84c2d6f05ce',
|
||||
lease_id: '',
|
||||
renewable: false,
|
||||
lease_duration: 0,
|
||||
data: {
|
||||
dr: {
|
||||
cluster_id: '7222cbbf-3fb3-949b-8e03-cd5a15babde6',
|
||||
corrupted_merkle_tree: false,
|
||||
known_primary_cluster_addrs: null,
|
||||
last_corruption_check_epoch: '-62135596800',
|
||||
last_reindex_epoch: '0',
|
||||
merkle_root: 'd3ae75bde029e05d435f92b9ecc5641c1b027cc4',
|
||||
mode: 'secondary',
|
||||
primaries: [],
|
||||
primary_cluster_addr: '',
|
||||
secondary_id: '',
|
||||
ssct_generation_counter: 0,
|
||||
state: 'idle',
|
||||
},
|
||||
performance: {
|
||||
mode: 'disabled',
|
||||
},
|
||||
},
|
||||
wrap_info: null,
|
||||
warnings: null,
|
||||
auth: null,
|
||||
mount_type: '',
|
||||
}));
|
||||
this.server.get('/sys/replication/dr/status', () => ({
|
||||
data: { mode: 'secondary', cluster_id: 'dr-cluster-id' },
|
||||
}));
|
||||
this.server.get('/sys/health', () => ({
|
||||
initialized: true,
|
||||
sealed: false,
|
||||
standby: false,
|
||||
performance_standby: false,
|
||||
replication_performance_mode: 'disabled',
|
||||
replication_dr_mode: 'secondary',
|
||||
server_time_utc: 1754948244,
|
||||
version: '1.21.0-beta1+ent',
|
||||
enterprise: true,
|
||||
cluster_name: 'vault-cluster-64853bcd',
|
||||
cluster_id: '113a6c47-077f-bea7-0e8e-70a91821e85a',
|
||||
last_wal: 82,
|
||||
license: {
|
||||
state: 'autoloaded',
|
||||
expiry_time: '2029-01-27T00:00:00Z',
|
||||
terminated: false,
|
||||
},
|
||||
echo_duration_ms: 0,
|
||||
clock_skew_ms: 0,
|
||||
replication_primary_canary_age_ms: 0,
|
||||
removed_from_cluster: false,
|
||||
}));
|
||||
this.server.get('/sys/seal-status', () => ({
|
||||
type: 'shamir',
|
||||
initialized: true,
|
||||
sealed: false,
|
||||
t: 1,
|
||||
n: 1,
|
||||
progress: 0,
|
||||
nonce: '',
|
||||
version: '1.21.0-beta1+ent',
|
||||
build_date: '2025-08-11T14:11:00Z',
|
||||
migration: false,
|
||||
cluster_name: 'vault-cluster-64853bcd',
|
||||
cluster_id: '113a6c47-077f-bea7-0e8e-70a91821e85a',
|
||||
recovery_seal: false,
|
||||
storage_type: 'raft',
|
||||
removed_from_cluster: false,
|
||||
}));
|
||||
|
||||
await click(GENERAL.button('demote'));
|
||||
await pollCluster(this.owner); // We must poll the cluster to stimulate a cluster reload. This is skipped in ember testing so must be forced.
|
||||
|
||||
// Spy on the route's addAnalyticsService method
|
||||
const clusterRoute = this.owner.lookup('route:vault.cluster');
|
||||
const addAnalyticsSpy = sinon.spy(clusterRoute, 'addAnalyticsService');
|
||||
|
||||
// Visit the DR secondary view. This is the route used only by DR secondaries
|
||||
await visit('/vault/replication-dr-promote');
|
||||
|
||||
assert
|
||||
.dom('[data-test-promote-description]')
|
||||
.hasText(
|
||||
'Promote this cluster to a Disaster Recovery primary',
|
||||
'shows the correct description for a DR secondary'
|
||||
);
|
||||
assert.dom('[data-test-mode]').includesText('secondary', 'shows the DR secondary mode badge');
|
||||
|
||||
await click(GENERAL.linkTo('Details'));
|
||||
assert
|
||||
.dom('[data-test-replication-secondary-card]')
|
||||
.hasClass(
|
||||
'has-error-border',
|
||||
'shows error border on status because the DR secondary is not connected to a primary.'
|
||||
);
|
||||
|
||||
// Assert addAnalyticsService was NOT called
|
||||
assert.false(addAnalyticsSpy.called, 'addAnalyticsService should not be called on DR secondary');
|
||||
|
||||
// Restore spy
|
||||
addAnalyticsSpy.restore();
|
||||
});
|
||||
});
|
||||
|
|
@ -9,9 +9,9 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
|
|||
import { click, currentURL, settled, visit, waitFor } from '@ember/test-helpers';
|
||||
import { login } from 'vault/tests/helpers/auth/auth-helpers';
|
||||
import { STATUS_DISABLED_RESPONSE, mockReplicationBlock } from 'vault/tests/helpers/replication';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
|
||||
const s = {
|
||||
navLink: (title) => `[data-test-sidebar-nav-link="${title}"]`,
|
||||
title: (t) => `[data-test-replication-title="${t}"]`,
|
||||
detailLink: (mode) => `[data-test-replication-details-link="${mode}"]`,
|
||||
summaryCard: '[data-test-replication-summary-card]',
|
||||
|
|
@ -53,8 +53,8 @@ module('Acceptance | Enterprise | replication modes', function (hooks) {
|
|||
await assertTitle(assert, 'Replication unsupported');
|
||||
|
||||
// Nav links
|
||||
assert.dom(s.navLink('Performance')).doesNotExist('hides performance link');
|
||||
assert.dom(s.navLink('Disaster Recovery')).doesNotExist('hides dr link');
|
||||
assert.dom(GENERAL.navLink('Performance')).doesNotExist('hides performance link');
|
||||
assert.dom(GENERAL.navLink('Disaster Recovery')).doesNotExist('hides dr link');
|
||||
});
|
||||
|
||||
test('replication page when disabled', async function (assert) {
|
||||
|
|
@ -64,15 +64,15 @@ module('Acceptance | Enterprise | replication modes', function (hooks) {
|
|||
await assertTitle(assert, 'Enable Replication');
|
||||
|
||||
// Nav links
|
||||
assert.dom(s.navLink('Performance')).exists('shows performance link');
|
||||
assert.dom(s.navLink('Disaster Recovery')).exists('shows dr link');
|
||||
assert.dom(GENERAL.navLink('Performance')).exists('shows performance link');
|
||||
assert.dom(GENERAL.navLink('Disaster Recovery')).exists('shows dr link');
|
||||
|
||||
await click(s.navLink('Performance'));
|
||||
await click(GENERAL.navLink('Performance'));
|
||||
assert.strictEqual(currentURL(), '/vault/replication/performance', 'it navigates to the correct page');
|
||||
await settled();
|
||||
assert.dom(s.enableForm).exists();
|
||||
|
||||
await click(s.navLink('Disaster Recovery'));
|
||||
await click(GENERAL.navLink('Disaster Recovery'));
|
||||
|
||||
await assertTitle(assert, 'Enable Disaster Recovery Replication', 'dr');
|
||||
});
|
||||
|
|
@ -90,21 +90,21 @@ module('Acceptance | Enterprise | replication modes', function (hooks) {
|
|||
assert.dom(s.detailLink('dr')).hasText('Enable', 'CTA to enable dr');
|
||||
|
||||
// Nav links
|
||||
assert.dom(s.navLink('Performance')).exists('shows performance link');
|
||||
assert.dom(s.navLink('Disaster Recovery')).exists('shows dr link');
|
||||
assert.dom(GENERAL.navLink('Performance')).exists('shows performance link');
|
||||
assert.dom(GENERAL.navLink('Disaster Recovery')).exists('shows dr link');
|
||||
|
||||
await click(s.navLink('Performance'));
|
||||
await click(GENERAL.navLink('Performance'));
|
||||
assert.strictEqual(currentURL(), `/vault/replication/performance`, `goes to correct URL`);
|
||||
await waitFor(s.dashboard);
|
||||
assert.dom(s.dashboard).exists(`it shows the replication dashboard`);
|
||||
|
||||
await click(s.navLink('Disaster Recovery'));
|
||||
await click(GENERAL.navLink('Disaster Recovery'));
|
||||
await assertTitle(assert, 'Enable Disaster Recovery Replication', 'dr');
|
||||
assert.dom(s.enableForm).exists('it shows the enable view for dr');
|
||||
});
|
||||
});
|
||||
// DR secondary mode is a whole other thing, test primary only here
|
||||
test(`replication page when dr primary only`, async function (assert) {
|
||||
|
||||
test('replication page when dr primary only', async function (assert) {
|
||||
await this.setupMocks({
|
||||
dr: mockReplicationBlock('primary'),
|
||||
performance: mockReplicationBlock(),
|
||||
|
|
@ -115,20 +115,20 @@ module('Acceptance | Enterprise | replication modes', function (hooks) {
|
|||
assert.dom(s.detailLink('dr')).hasText('Details', 'CTA to see dr details');
|
||||
|
||||
// Nav links
|
||||
assert.dom(s.navLink('Performance')).exists('shows performance link');
|
||||
assert.dom(s.navLink('Disaster Recovery')).exists('shows dr link');
|
||||
assert.dom(GENERAL.navLink('Performance')).exists('shows performance link');
|
||||
assert.dom(GENERAL.navLink('Disaster Recovery')).exists('shows dr link');
|
||||
|
||||
await click(s.navLink('Performance'));
|
||||
await click(GENERAL.navLink('Performance'));
|
||||
assert.strictEqual(currentURL(), `/vault/replication/performance`, `goes to correct URL`);
|
||||
await waitFor(s.enableForm);
|
||||
assert.dom(s.enableForm).exists('it shows the enable view for performance');
|
||||
|
||||
await click(s.navLink('Disaster Recovery'));
|
||||
await click(GENERAL.navLink('Disaster Recovery'));
|
||||
await assertTitle(assert, 'Disaster Recovery primary', 'Disaster Recovery');
|
||||
assert.dom(s.dashboard).exists(`it shows the replication dashboard`);
|
||||
});
|
||||
|
||||
test(`replication page both primary`, async function (assert) {
|
||||
test('replication page both primary', async function (assert) {
|
||||
await this.setupMocks({
|
||||
dr: mockReplicationBlock('primary'),
|
||||
performance: mockReplicationBlock('primary'),
|
||||
|
|
@ -137,11 +137,11 @@ module('Acceptance | Enterprise | replication modes', function (hooks) {
|
|||
await assertTitle(assert, 'Disaster Recovery & Performance primary', 'Disaster Recovery & Performance');
|
||||
assert.dom(s.summaryCard).exists({ count: 2 }, 'shows 2 summary cards');
|
||||
|
||||
await click(s.navLink('Performance'));
|
||||
await click(GENERAL.navLink('Performance'));
|
||||
await assertTitle(assert, 'Performance primary', 'Performance');
|
||||
assert.dom(s.enableForm).doesNotExist();
|
||||
|
||||
await click(s.navLink('Disaster Recovery'));
|
||||
await click(GENERAL.navLink('Disaster Recovery'));
|
||||
await assertTitle(assert, 'Disaster Recovery primary', 'Disaster Recovery');
|
||||
assert.dom(s.enableForm).doesNotExist();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import { login } from 'vault/tests/helpers/auth/auth-helpers';
|
|||
import { pollCluster } from 'vault/tests/helpers/poll-cluster';
|
||||
import { addSecondary, disableReplication, enableReplication } from 'vault/tests/helpers/replication';
|
||||
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
||||
import sinon from 'sinon';
|
||||
|
||||
// note: dr secondary tests are done in ./replication-dr-secondaries-test.js
|
||||
module('Acceptance | Enterprise | replication', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
|
||||
|
|
@ -30,7 +32,103 @@ module('Acceptance | Enterprise | replication', function (hooks) {
|
|||
await settled();
|
||||
});
|
||||
|
||||
test('it shows DR empty state when DR is not configured', async function (assert) {
|
||||
test('DR primary: enables primary and adds secondary', async function (assert) {
|
||||
const secondaryName = 'drSecondary';
|
||||
|
||||
await enableReplication('dr', 'primary');
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
assert
|
||||
.dom('[data-test-replication-title="Disaster Recovery"]')
|
||||
.includesText('Disaster Recovery', 'it displays the replication type correctly');
|
||||
assert
|
||||
.dom('[data-test-replication-mode-display]')
|
||||
.includesText('primary', 'it displays the cluster mode correctly');
|
||||
|
||||
await addSecondary(secondaryName);
|
||||
// modal for copying the token appears
|
||||
await click(GENERAL.button('Copy token')); // must copy token before escaping the modal.
|
||||
await click(GENERAL.cancelButton);
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
|
||||
await click('[data-test-replication-link="secondaries"]');
|
||||
assert
|
||||
.dom('[data-test-secondary-name]')
|
||||
.includesText(secondaryName, 'it displays the secondary in the list of known secondaries');
|
||||
|
||||
// verify overflow-wrap class is applied to secondary name
|
||||
assert
|
||||
.dom('[data-test-secondary-name]')
|
||||
.hasClass('overflow-wrap', 'it applies the overflow-wrap class to the secondary name');
|
||||
});
|
||||
|
||||
test('DR primary: shows demotion warning when Performance replication is active', async function (assert) {
|
||||
await visit('vault/replication/performance');
|
||||
|
||||
// enable perf replication
|
||||
await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
|
||||
await click(GENERAL.submitButton);
|
||||
|
||||
await pollCluster(this.owner);
|
||||
|
||||
// enable dr replication
|
||||
await visit('/vault/replication/dr');
|
||||
|
||||
await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
|
||||
|
||||
await click(GENERAL.submitButton);
|
||||
|
||||
await pollCluster(this.owner);
|
||||
await visit('/vault/replication/dr/manage');
|
||||
|
||||
await click(GENERAL.button('demote'));
|
||||
assert.ok(findAll('[data-test-demote-warning]').length, 'displays the demotion warning');
|
||||
});
|
||||
|
||||
test('DR primary: shows empty state when secondary mode is not enabled and we navigated to the secondary details page', async function (assert) {
|
||||
// enable dr replication
|
||||
|
||||
await visit('/vault/replication/dr');
|
||||
|
||||
await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
|
||||
await click(GENERAL.submitButton);
|
||||
await settled(); // eslint-disable-line
|
||||
await pollCluster(this.owner);
|
||||
await visit('/vault/replication-dr-promote/details');
|
||||
|
||||
assert
|
||||
.dom('[data-test-component="empty-state"]')
|
||||
.exists('Empty state is shown when no secondary is configured');
|
||||
assert
|
||||
.dom('[data-test-empty-state-message]')
|
||||
.hasText(
|
||||
'This Disaster Recovery secondary has not been enabled. You can do so from the Disaster Recovery Primary.',
|
||||
'Renders the correct message for when a primary is enabled but no secondary is configured and we have navigated to the secondary details page.'
|
||||
);
|
||||
});
|
||||
|
||||
test('DR primary: runs analytics service when enabled', async function (assert) {
|
||||
// Spy on the route's addAnalyticsService method if needed
|
||||
const clusterRoute = this.owner.lookup('route:vault.cluster');
|
||||
const addAnalyticsSpy = sinon.spy(clusterRoute, 'addAnalyticsService');
|
||||
|
||||
// Set up DR replication as primary
|
||||
await enableReplication('dr', 'primary');
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
|
||||
// Visit the route that triggers analytics
|
||||
await visit('/vault/replication/replication/dr'); // or the correct route for your app
|
||||
|
||||
// Verify that analytics service was called
|
||||
assert.true(addAnalyticsSpy.called, 'addAnalyticsService should be called on DR primary');
|
||||
|
||||
// Clean up spy
|
||||
addAnalyticsSpy.restore();
|
||||
});
|
||||
|
||||
test('DR secondary: shows empty state when replication is not enabled', async function (assert) {
|
||||
await visit('/vault/replication-dr-promote/details');
|
||||
|
||||
assert.dom('[data-test-component="empty-state"]').exists();
|
||||
|
|
@ -46,7 +144,7 @@ module('Acceptance | Enterprise | replication', function (hooks) {
|
|||
);
|
||||
});
|
||||
|
||||
test('Performance replication: add secondary and delete config', async function (assert) {
|
||||
test('Performance primary: add secondary and delete config', async function (assert) {
|
||||
const secondaryName = `performanceSecondary`;
|
||||
const mode = 'deny';
|
||||
|
||||
|
|
@ -56,11 +154,12 @@ module('Acceptance | Enterprise | replication', function (hooks) {
|
|||
// confirm that the details dashboard shows
|
||||
await waitFor('[data-test-replication-dashboard]', 2000);
|
||||
await addSecondary(secondaryName, mode);
|
||||
await click(GENERAL.button('Copy token')); // must copy token before escaping the modal.
|
||||
await click(GENERAL.cancelButton);
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
|
||||
await click('[data-test-replication-link="secondaries"]');
|
||||
await click(GENERAL.button('Copy token'));
|
||||
await click(GENERAL.cancelButton);
|
||||
await click(GENERAL.menuTrigger);
|
||||
await click('[data-test-replication-path-filter-link]');
|
||||
assert.strictEqual(
|
||||
|
|
@ -87,80 +186,7 @@ module('Acceptance | Enterprise | replication', function (hooks) {
|
|||
.exists('shows a table row the recently added secondary');
|
||||
});
|
||||
|
||||
test('DR replication: enable and add secondary', async function (assert) {
|
||||
const secondaryName = 'drSecondary';
|
||||
|
||||
await enableReplication('dr', 'primary');
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
assert
|
||||
.dom('[data-test-replication-title="Disaster Recovery"]')
|
||||
.includesText('Disaster Recovery', 'it displays the replication type correctly');
|
||||
assert
|
||||
.dom('[data-test-replication-mode-display]')
|
||||
.includesText('primary', 'it displays the cluster mode correctly');
|
||||
|
||||
await addSecondary(secondaryName);
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
|
||||
await click('[data-test-replication-link="secondaries"]');
|
||||
await click(GENERAL.button('Copy token'));
|
||||
await click(GENERAL.cancelButton);
|
||||
assert
|
||||
.dom('[data-test-secondary-name]')
|
||||
.includesText(secondaryName, 'it displays the secondary in the list of known secondaries');
|
||||
|
||||
// verify overflow-wrap class is applied to secondary name
|
||||
assert
|
||||
.dom('[data-test-secondary-name]')
|
||||
.hasClass('overflow-wrap', 'it applies the overflow-wrap class to the secondary name');
|
||||
});
|
||||
|
||||
test('disabling dr primary when perf replication is enabled', async function (assert) {
|
||||
await visit('vault/replication/performance');
|
||||
|
||||
// enable perf replication
|
||||
await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
|
||||
await click(GENERAL.submitButton);
|
||||
|
||||
await pollCluster(this.owner);
|
||||
|
||||
// enable dr replication
|
||||
await visit('/vault/replication/dr');
|
||||
|
||||
await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
|
||||
|
||||
await click(GENERAL.submitButton);
|
||||
|
||||
await pollCluster(this.owner);
|
||||
await visit('/vault/replication/dr/manage');
|
||||
|
||||
await click(GENERAL.button('demote'));
|
||||
assert.ok(findAll('[data-test-demote-warning]').length, 'displays the demotion warning');
|
||||
});
|
||||
|
||||
test('navigating to dr secondary details page when dr secondary is not enabled', async function (assert) {
|
||||
// enable dr replication
|
||||
|
||||
await visit('/vault/replication/dr');
|
||||
|
||||
await fillIn('[data-test-replication-cluster-mode-select]', 'primary');
|
||||
await click(GENERAL.submitButton);
|
||||
await settled(); // eslint-disable-line
|
||||
await pollCluster(this.owner);
|
||||
await visit('/vault/replication-dr-promote/details');
|
||||
|
||||
assert.dom('[data-test-component="empty-state"]').exists();
|
||||
assert
|
||||
.dom('[data-test-empty-state-message]')
|
||||
.hasText(
|
||||
'This Disaster Recovery secondary has not been enabled. You can do so from the Disaster Recovery Primary.',
|
||||
'renders message when replication is enabled'
|
||||
);
|
||||
});
|
||||
|
||||
test('add secondary and navigate through token generation modal', async function (assert) {
|
||||
test('Performance primary: manages secondary token generation and TTL configuration', async function (assert) {
|
||||
const secondaryNameFirst = 'firstSecondary';
|
||||
const secondaryNameSecond = 'secondSecondary';
|
||||
|
||||
|
|
@ -188,7 +214,7 @@ module('Acceptance | Enterprise | replication', function (hooks) {
|
|||
await fillIn('[data-test-input="Secondary ID"]', secondaryNameSecond);
|
||||
await click(GENERAL.toggleInput('Time to Live (TTL) for generated secondary token'));
|
||||
await fillIn('[data-test-ttl-value]', 3);
|
||||
await click('[data-test-secondary-add]');
|
||||
await click(GENERAL.submitButton);
|
||||
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
|
|
@ -207,8 +233,36 @@ module('Acceptance | Enterprise | replication', function (hooks) {
|
|||
.includesText(secondaryNameFirst, 'it displays the secondary in the list of secondaries');
|
||||
});
|
||||
|
||||
test('render performance and dr primary and navigate to details page', async function (assert) {
|
||||
// enable perf primary replication
|
||||
test('Performance primary: demotes primary to secondary and displays correct status', async function (assert) {
|
||||
// enable perf replication
|
||||
await enableReplication('performance', 'primary');
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
|
||||
// demote perf primary to a secondary
|
||||
await click('[data-test-replication-link="manage"]');
|
||||
|
||||
// open demote modal
|
||||
await click(GENERAL.button('demote'));
|
||||
|
||||
// enter confirmation text
|
||||
await fillIn('[data-test-confirmation-modal-input="Demote to secondary?"]', 'Performance');
|
||||
// Click confirm button
|
||||
await click(GENERAL.confirmButton);
|
||||
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
|
||||
await click('[data-test-replication-link="details"]');
|
||||
await waitFor('[data-test-replication-dashboard]');
|
||||
assert.dom('[data-test-replication-dashboard]').exists();
|
||||
assert.dom('[data-test-selectable-card-container="secondary"]').exists();
|
||||
assert
|
||||
.dom('[data-test-replication-mode-display]')
|
||||
.hasText('secondary', 'it displays the cluster mode correctly in header');
|
||||
});
|
||||
|
||||
test('Replication Dashboard: displays summary cards for both Performance and DR primaries', async function (assert) {
|
||||
await enableReplication('performance', 'primary');
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
|
|
@ -246,33 +300,4 @@ module('Acceptance | Enterprise | replication', function (hooks) {
|
|||
.exists('shows the correct card on the details dashboard');
|
||||
assert.strictEqual(currentURL(), '/vault/replication/dr');
|
||||
});
|
||||
|
||||
test('render performance secondary and navigate to the details page', async function (assert) {
|
||||
// enable perf replication
|
||||
await enableReplication('performance', 'primary');
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
|
||||
// demote perf primary to a secondary
|
||||
await click('[data-test-replication-link="manage"]');
|
||||
|
||||
// open demote modal
|
||||
await click(GENERAL.button('demote'));
|
||||
|
||||
// enter confirmation text
|
||||
await fillIn('[data-test-confirmation-modal-input="Demote to secondary?"]', 'Performance');
|
||||
// Click confirm button
|
||||
await click(GENERAL.confirmButton);
|
||||
|
||||
await pollCluster(this.owner);
|
||||
await settled();
|
||||
|
||||
await click('[data-test-replication-link="details"]');
|
||||
await waitFor('[data-test-replication-dashboard]');
|
||||
assert.dom('[data-test-replication-dashboard]').exists();
|
||||
assert.dom('[data-test-selectable-card-container="secondary"]').exists();
|
||||
assert
|
||||
.dom('[data-test-replication-mode-display]')
|
||||
.hasText('secondary', 'it displays the cluster mode correctly in header');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const GENERAL = {
|
|||
confirmButton: '[data-test-confirm-button]', // used most often on modal or confirm popups
|
||||
confirmTrigger: '[data-test-confirm-action-trigger]',
|
||||
copyButton: '[data-test-copy-button]',
|
||||
// there should only be one save button per view (e.g. one per form) so this does not need to be dynamic
|
||||
// there should only be one submit button per view (e.g. one per form) so this does not need to be dynamic
|
||||
// this button should be used for any kind of "submit" on a form or "save" action.
|
||||
submitButton: '[data-test-submit]',
|
||||
button: (label: string) => (label ? `[data-test-button="${label}"]` : '[data-test-button]'),
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export async function addSecondary(secondaryName, mountFilterMode = null) {
|
|||
await searchSelect.options.objectAt(0).click();
|
||||
}
|
||||
|
||||
await click('[data-test-secondary-add]');
|
||||
await click(GENERAL.submitButton);
|
||||
}
|
||||
|
||||
export const disableReplication = async (type, assert) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue