[UI] Ember Data Migration: PKI Tidy (#11020) (#11049)

* updates pki tidy auto route to use api service

* updates pki tidy status view to use api service

* updates pki tidy auto and manual workflows to use api service and form class

Co-authored-by: Jordan Reimer <zofskeez@gmail.com>
This commit is contained in:
Vault Automation 2025-12-01 15:59:50 -05:00 committed by GitHub
parent 4e2f3ba489
commit 004d6da92c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 457 additions and 418 deletions

View file

@ -0,0 +1,23 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import OpenApiForm from 'vault/forms/open-api';
import type { PkiTidyRequest, PkiConfigureAutoTidyRequest } from '@hashicorp/vault-client-typescript';
type PkiTidyFormRequest = PkiTidyRequest | PkiConfigureAutoTidyRequest;
export default class PkiTidyForm extends OpenApiForm<PkiTidyFormRequest> {
constructor(...args: ConstructorParameters<typeof OpenApiForm>) {
super(...args);
// use ttl picker for pause_duration
const pauseDuration = this.formFields.find((field) => field.name === 'pause_duration');
if (pauseDuration) {
pauseDuration.options.editType = 'ttl';
this.data.pause_duration = '0';
}
}
}

View file

@ -16,8 +16,8 @@
</PageHeader>
<PkiTidyForm
@tidy={{@model}}
@form={{@form}}
@tidyType="auto"
@onSave={{transition-to "vault.cluster.secrets.backend.pki.tidy.auto"}}
@onCancel={{transition-to (concat "vault.cluster.secrets.backend.pki.tidy" (if @model.enabled ".auto" ""))}}
@onCancel={{transition-to (concat "vault.cluster.secrets.backend.pki.tidy" (if @form.data.enabled ".auto" ""))}}
/>

View file

@ -16,7 +16,7 @@
<Toolbar>
<ToolbarActions>
<LinkTo @route="tidy.auto.configure" @model={{@model.id}} class="toolbar-link" data-test-pki-edit-tidy-auto-link>
<LinkTo @route="tidy.auto.configure" @model={{@backend}} class="toolbar-link" data-test-pki-edit-tidy-auto-link>
Edit auto-tidy
<Icon @name="chevron-right" />
</LinkTo>
@ -24,20 +24,32 @@
</Toolbar>
<main>
{{#each @model.allGroups as |group|}}
{{#each (tidy-groups) as |group|}}
{{#each-in group as |label fields|}}
{{#if (not-eq label "autoTidy")}}
{{#if (not-eq label "default")}}
<h2 class="title is-5 has-top-margin-l has-bottom-margin-xs" data-test-group-title={{label}}>
{{label}}
</h2>
{{/if}}
{{#each fields as |attr|}}
{{#each fields as |field|}}
<InfoTableRow
@label={{or attr.options.detailsLabel attr.options.label (humanize (dasherize attr.name))}}
@value={{get @model attr.name}}
@formatTtl={{attr.options.formatTtl}}
data-test-row={{attr.name}}
@label={{tidy-field-label field}}
@value={{get @model field}}
@formatTtl={{includes
field
(array
"acme_account_safety_buffer"
"interval_duration"
"min_startup_backoff_duration"
"max_startup_backoff_duration"
"issuer_safety_buffer"
"pause_duration"
"revocation_queue_safety_buffer"
"safety_buffer"
)
}}
data-test-row={{field}}
/>
{{/each}}
{{/each-in}}

View file

@ -16,7 +16,7 @@
</PageHeader>
<PkiTidyForm
@tidy={{@model}}
@form={{@model}}
@tidyType="manual"
@onSave={{transition-to "vault.cluster.secrets.backend.pki.tidy"}}
@onCancel={{transition-to "vault.cluster.secrets.backend.pki.tidy"}}

View file

@ -6,11 +6,11 @@
<Toolbar>
<ToolbarActions>
<div class="toolbar-separator"></div>
{{#if @autoTidyConfig.enabled}}
<ToolbarLink @route="tidy.auto" @model={{@autoTidyConfig.id}} data-test-pki-auto-tidy-config>
{{#if @enabled}}
<ToolbarLink @route="tidy.auto" @model={{this.secretMountPath.currentPath}} data-test-pki-auto-tidy-config>
Auto-tidy configuration
</ToolbarLink>
<ToolbarLink @route="tidy.manual" @model={{@autoTidyConfig.id}} data-test-pki-manual-tidy-config>
<ToolbarLink @route="tidy.manual" @model={{this.secretMountPath.currentPath}} data-test-pki-manual-tidy-config>
Perform manual tidy
</ToolbarLink>
{{else}}
@ -149,13 +149,13 @@
<Hds::Button
@text="Automatic tidy"
@route="tidy.auto.configure"
@model={{@autoTidyConfig.id}}
@model={{this.secretMountPath.currentPath}}
data-test-tidy-modal-auto-button
/>
<Hds::Button
@text="Manual tidy"
@route="tidy.manual"
@model={{@autoTidyConfig.id}}
@model={{this.secretMountPath.currentPath}}
data-test-tidy-modal-manual-button
/>
<Hds::Button @text="Cancel" @color="secondary" {{on "click" F.close}} data-test-tidy-modal-cancel-button />

View file

@ -8,17 +8,15 @@ import { tracked } from '@glimmer/tracking';
import { service } from '@ember/service';
import { task } from 'ember-concurrency';
import { waitFor } from '@ember/test-waiters';
import errorMessage from 'vault/utils/error-message';
import type Store from '@ember-data/store';
import type ApiService from 'vault/services/api';
import type SecretMountPath from 'vault/services/secret-mount-path';
import type FlashMessageService from 'vault/services/flash-messages';
import type VersionService from 'vault/services/version';
import type PkiTidyModel from 'vault/models/pki/tidy';
import type RouterService from '@ember/routing/router-service';
interface Args {
autoTidyConfig: PkiTidyModel;
enabled: boolean;
tidyStatus: TidyStatusParams;
}
@ -48,7 +46,7 @@ interface TidyStatusParams {
}
export default class PkiTidyStatusComponent extends Component<Args> {
@service declare readonly store: Store;
@service declare readonly api: ApiService;
@service declare readonly secretMountPath: SecretMountPath;
@service declare readonly flashMessages: FlashMessageService;
@service declare readonly version: VersionService;
@ -92,7 +90,7 @@ export default class PkiTidyStatusComponent extends Component<Args> {
get hasTidyConfig() {
return !this.tidyStatusConfigFields.every(
(attr) => this.args.tidyStatus[attr as keyof TidyStatusParams] === null
(attr) => this.args.tidyStatus[attr as keyof TidyStatusParams] === undefined
);
}
@ -147,17 +145,17 @@ export default class PkiTidyStatusComponent extends Component<Args> {
}
}
@task
@waitFor
*cancelTidy() {
try {
const tidyAdapter = this.store.adapterFor('pki/tidy');
yield tidyAdapter.cancelTidy(this.secretMountPath.currentPath);
this.router.transitionTo('vault.cluster.secrets.backend.pki.tidy');
} catch (error) {
this.flashMessages.danger(errorMessage(error));
} finally {
this.confirmCancelTidy = false;
}
}
cancelTidy = task(
waitFor(async () => {
try {
await this.api.secrets.pkiTidyCancel(this.secretMountPath.currentPath);
this.router.transitionTo('vault.cluster.secrets.backend.pki.tidy');
} catch (error) {
const { message } = await this.api.parseError(error);
this.flashMessages.danger(message);
} finally {
this.confirmCancelTidy = false;
}
})
);
}

View file

@ -5,8 +5,9 @@
<hr class="is-marginless has-background-gray-200" />
<p class="has-top-margin-m has-bottom-margin-l">Tidying cleans up the storage backend and/or CRL by removing certificates
that have expired and are past a certain buffer period beyond their expiration time.
<p class="has-top-margin-m has-bottom-margin-l">
Tidying cleans up the storage backend and/or CRL by removing certificates that have expired and are past a certain buffer
period beyond their expiration time.
<DocLink @path="/vault/api-docs/secret/pki#{{if (eq @tidyType 'manual') 'tidy' 'configure-automatic-tidy'}}">Learn more</DocLink>
</p>
@ -14,52 +15,55 @@
<form class="has-bottom-margin-s" {{on "submit" (perform this.save)}} data-test-tidy-form={{@tidyType}}>
{{#if (eq @tidyType "auto")}}
{{#let (get @tidy.allByKey "enabled") as |enabledAttr|}}
<div class="field">
<Toggle @onChange={{fn (mut @tidy.enabled)}} @checked={{@tidy.enabled}} @name={{enabledAttr.name}}>
<legend>
<span class="ttl-picker-label is-large">
{{if @tidy.enabled enabledAttr.options.label enabledAttr.options.labelDisabled}}
</span>
{{#unless @tidy.enabled}}
<p class="sub-text">{{enabledAttr.options.helperTextDisabled}}</p>
{{/unless}}
</legend>
</Toggle>
</div>
{{/let}}
{{#if @tidy.enabled}}
<div class="field">
<Toggle @onChange={{fn (mut @form.data.enabled)}} @checked={{@form.data.enabled}} @name="enabled">
<legend>
<span class="ttl-picker-label is-large">
{{if @form.data.enabled "Automatic tidy enabled" "Automatic tidy disabled"}}
</span>
{{#unless @form.data.enabled}}
<p class="sub-text">Automatic tidy operations will not run.</p>
{{/unless}}
</legend>
</Toggle>
</div>
{{#if @form.data.enabled}}
<h2 class="title is-size-5 has-border-bottom-light page-header" data-test-tidy-header="Automatic tidy settings">
Automatic tidy settings
</h2>
{{#each @tidy.autoTidyConfigFields as |field|}}
<FormField @attr={{get @tidy.allByKey field}} @model={{@tidy}} />
{{#each (tidy-groups "auto") as |fieldGroup|}}
{{#each-in fieldGroup as |group fields|}}
{{#each fields as |field|}}
<FormField @attr={{find-by "name" field @form.formFields}} @model={{@form}} />
{{/each}}
{{/each-in}}
{{/each}}
{{/if}}
{{/if}}
{{#if (or (eq @tidyType "manual") @tidy.enabled)}}
{{#each @tidy.formFieldGroups as |fieldGroup|}}
{{#if (or (eq @tidyType "manual") @form.data.enabled)}}
{{#each (tidy-groups "manual") as |fieldGroup|}}
{{#each-in fieldGroup as |group fields|}}
<h2 class="title is-size-5 has-border-bottom-light page-header" data-test-tidy-header={{group}}>
{{group}}
</h2>
{{#each fields as |attr|}}
{{#if (eq attr.name "acmeAccountSafetyBuffer")}}
{{#each fields as |field|}}
{{#if (eq field "acme_account_safety_buffer")}}
<TtlPicker
data-test-input={{attr.name}}
@onChange={{fn this.handleTtl attr}}
@label={{attr.options.label}}
@labelDisabled={{attr.options.labelDisabled}}
@helperTextDisabled={{attr.options.helperTextDisabled}}
@helperTextEnabled={{attr.options.helperTextEnabled}}
@initialEnabled={{get @tidy attr.options.mapToBoolean}}
@initialValue={{get @tidy attr.name}}
data-test-input={{field}}
@onChange={{this.handleAcmeTtl}}
@label="Tidy ACME enabled"
@labelDisabled="Tidy ACME disabled"
@helperTextDisabled="Tidying of ACME accounts, orders and authorizations is disabled."
@helperTextEnabled="The amount of time that must pass after creation that an account with no orders is marked revoked, and the amount of time after being marked revoked or deactivated."
@initialEnabled={{@form.data.tidy_acme}}
@initialValue={{get @form.data field}}
/>
{{else}}
{{! tidyAcme is handled by the ttl above }}
{{#if (not-eq attr.name "tidyAcme")}}
<FormField @attr={{attr}} @model={{@tidy}} />
{{! tidy_acme is set via the TtlPicker change event above }}
{{#if (not-eq field "tidy_acme")}}
<FormField @attr={{find-by "name" field @form.formFields}} @model={{@form}} />
{{/if}}
{{/if}}
{{/each}}

View file

@ -4,7 +4,6 @@
*/
import Component from '@glimmer/component';
import errorMessage from 'vault/utils/error-message';
import { action } from '@ember/object';
import { service } from '@ember/service';
import { task } from 'ember-concurrency';
@ -12,47 +11,51 @@ import { waitFor } from '@ember/test-waiters';
import { tracked } from '@glimmer/tracking';
import type RouterService from '@ember/routing/router-service';
import type PkiTidyModel from 'vault/models/pki/tidy';
import type { FormField, TtlEvent } from 'vault/app-types';
import type PkiTidyForm from 'vault/forms/secrets/pki/tidy';
import type { TtlEvent } from 'vault/app-types';
import type ApiService from 'vault/services/api';
import type SecretMountPathService from 'vault/services/secret-mount-path';
interface Args {
tidy: PkiTidyModel;
form: PkiTidyForm;
tidyType: string;
onSave: CallableFunction;
onCancel: CallableFunction;
}
interface PkiTidyTtls {
acmeAccountSafetyBuffer: string;
}
interface PkiTidyBooleans {
tidyAcme: boolean;
}
export default class PkiTidyForm extends Component<Args> {
export default class PkiTidyFormComponent extends Component<Args> {
@service('app-router') declare readonly router: RouterService;
@service declare readonly api: ApiService;
@service declare readonly secretMountPath: SecretMountPathService;
@tracked errorBanner = '';
@tracked invalidFormAlert = '';
@task
@waitFor
*save(event: Event) {
event.preventDefault();
try {
yield this.args.tidy.save({ adapterOptions: { tidyType: this.args.tidyType } });
this.args.onSave();
} catch (e) {
this.errorBanner = errorMessage(e);
this.invalidFormAlert = 'There was an error submitting this form.';
}
}
save = task(
waitFor(async (event: Event) => {
event.preventDefault();
try {
const { currentPath } = this.secretMountPath;
const { data } = this.args.form.toJSON();
if (this.args.tidyType === 'auto') {
await this.api.secrets.pkiConfigureAutoTidy(currentPath, data);
} else {
await this.api.secrets.pkiTidy(currentPath, data);
}
this.args.onSave();
} catch (e) {
const { message } = await this.api.parseError(e);
this.errorBanner = message;
this.invalidFormAlert = 'There was an error submitting this form.';
}
})
);
@action
handleTtl(attr: FormField, e: TtlEvent) {
handleAcmeTtl(e: TtlEvent) {
const { enabled, goSafeTimeString } = e;
const ttlAttr = attr.name;
this.args.tidy[ttlAttr as keyof PkiTidyTtls] = goSafeTimeString;
this.args.tidy[attr.options.mapToBoolean as keyof PkiTidyBooleans] = enabled;
this.args.form.data.acme_account_safety_buffer = goSafeTimeString;
this.args.form.data.tidy_acme = enabled;
}
}

View file

@ -0,0 +1,23 @@
/**
* Copyright IBM Corp. 2016, 2025
* SPDX-License-Identifier: BUSL-1.1
*/
import { toLabel } from 'core/helpers/to-label';
export default function tidyFieldLabel(field: string) {
const label = toLabel([field]);
return (
{
acme_account_safety_buffer: 'ACME account safety buffer',
tidy_acme: 'Tidy ACME',
enabled: 'Automatic tidy enabled',
tidy_cert_store: 'Tidy the certificate store',
tidy_cross_cluster_revoked_certs: 'Tidy cross-cluster revoked certificates',
tidy_cmpv2_nonce_store: 'Tidy CMPv2 nonce store',
tidy_move_legacy_ca_bundle: 'Tidy legacy CA bundle',
tidy_revocation_queue: 'Tidy cross-cluster revocation requests',
tidy_revoked_cert_issuer_associations: 'Tidy revoked certificate issuer associations',
tidy_revoked_certs: 'Tidy revoked certificates',
}[field] || label
);
}

View file

@ -0,0 +1,62 @@
/**
* Copyright IBM Corp. 2016, 2025
* SPDX-License-Identifier: BUSL-1.1
*/
import Helper from '@ember/component/helper';
import { service } from '@ember/service';
import type VersionService from 'vault/services/version';
export default class TidyGroups extends Helper {
@service declare readonly version: VersionService;
compute([group]: [group?: 'auto' | 'manual']) {
// groups that are shared between auto and manual tidy types
const shared: Array<Record<string, Array<string>>> = [
{
'Universal operations': [
'tidy_cert_store',
'tidy_cert_metadata',
'tidy_revoked_certs',
'tidy_revoked_cert_issuer_associations',
'safety_buffer',
'pause_duration',
],
},
{
'ACME operations': ['tidy_acme', 'acme_account_safety_buffer'],
},
{
'Issuer operations': ['tidy_expired_issuers', 'tidy_move_legacy_ca_bundle', 'issuer_safety_buffer'],
},
];
// cross cluster operations are only available in enterprise
if (this.version.isEnterprise) {
shared.push({
'Cross-cluster operations': [
'tidy_revocation_queue',
'tidy_cross_cluster_revoked_certs',
'tidy_cmpv2_nonce_store',
'revocation_queue_safety_buffer',
],
});
}
// auto tidy specific fields
const auto = {
default: ['interval_duration', 'min_startup_backoff_duration', 'max_startup_backoff_duration'],
};
if (group === 'auto') {
return [auto];
}
if (group === 'manual') {
return shared;
}
// if group is not specified, return combined groups
// add enabled field to the top of auto tidy fields for details view
auto.default.unshift('enabled');
shared.unshift(auto);
return shared;
}
}

View file

@ -6,18 +6,19 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import { withConfig } from 'pki/decorators/check-issuers';
import { hash } from 'rsvp';
@withConfig()
export default class PkiTidyRoute extends Route {
@service store;
@service api;
model() {
async model() {
const engine = this.modelFor('application');
return hash({
const autoTidyConfig = await this.api.secrets.pkiReadAutoTidyConfiguration(engine.id);
return {
hasConfig: this.pkiMountHasConfig,
engine,
autoTidyConfig: this.store.findRecord('pki/tidy', engine.id),
});
autoTidyConfig,
};
}
}

View file

@ -5,25 +5,27 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import { withConfirmLeave } from 'core/decorators/confirm-leave';
import PkiTidyForm from 'vault/forms/secrets/pki/tidy';
@withConfirmLeave()
export default class PkiTidyAutoConfigureRoute extends Route {
@service store;
@service secretMountPath;
// inherits model from tidy/auto
model() {
const { autoTidyConfig } = this.modelFor('tidy');
return new PkiTidyForm('PkiConfigureAutoTidyRequest', autoTidyConfig);
}
setupController(controller, resolvedModel) {
// autoTidyConfig id is the backend path
const { id: backend } = resolvedModel;
const { currentPath } = this.secretMountPath;
super.setupController(controller, resolvedModel);
controller.breadcrumbs = [
{ label: 'Secrets', route: 'secrets', linkExternal: true },
{ label: this.secretMountPath.currentPath, route: 'overview', model: backend },
{ label: 'Configuration', route: 'configuration.index', model: backend },
{ label: 'Tidy', route: 'tidy', model: backend },
{ label: 'Auto', route: 'tidy.auto', model: backend },
{ label: currentPath, route: 'overview', model: currentPath },
{ label: 'Configuration', route: 'configuration.index', model: currentPath },
{ label: 'Tidy', route: 'tidy', model: currentPath },
{ label: 'Auto', route: 'tidy.auto', model: currentPath },
{ label: 'Configure' },
];
}

View file

@ -13,15 +13,14 @@ export default class TidyAutoIndexRoute extends Route {
// inherits model from tidy/auto
setupController(controller, resolvedModel) {
// autoTidyConfig id is the backend path
const { id: backend } = resolvedModel;
super.setupController(...arguments);
const { currentPath } = this.secretMountPath;
super.setupController(controller, resolvedModel);
controller.breadcrumbs = [
{ label: 'Secrets', route: 'secrets', linkExternal: true },
{ label: this.secretMountPath.currentPath, route: 'overview', model: backend },
{ label: 'Tidy', route: 'tidy.index', model: backend },
{ label: currentPath, route: 'overview', model: currentPath },
{ label: 'Tidy', route: 'tidy.index', model: currentPath },
{ label: 'Auto' },
];
controller.title = this.secretMountPath.currentPath;
controller.backend = currentPath;
}
}

View file

@ -4,35 +4,28 @@
*/
import Route from '@ember/routing/route';
import { PKI_DEFAULT_EMPTY_STATE_MSG } from '../overview';
import { hash } from 'rsvp';
import { service } from '@ember/service';
import { PKI_DEFAULT_EMPTY_STATE_MSG } from '../overview';
import timestamp from 'core/utils/timestamp';
export default class PkiTidyIndexRoute extends Route {
@service store;
@service api;
@service secretMountPath;
async fetchTidyStatus() {
const adapter = this.store.adapterFor('application');
const tidyStatusResponse = await adapter.ajax(
`/v1/${this.secretMountPath.currentPath}/tidy-status`,
'GET'
);
const responseTimestamp = timestamp.now();
tidyStatusResponse.data.responseTimestamp = responseTimestamp;
return tidyStatusResponse.data;
const status = await this.api.secrets.pkiTidyStatus(this.secretMountPath.currentPath);
return { ...status, responseTimestamp: timestamp.now() };
}
model() {
async model() {
const { hasConfig, autoTidyConfig, engine } = this.modelFor('tidy');
const tidyStatus = await this.fetchTidyStatus();
return hash({
tidyStatus: this.fetchTidyStatus(),
return {
tidyStatus,
hasConfig,
autoTidyConfig,
engine,
});
};
}
setupController(controller, resolvedModel) {

View file

@ -5,24 +5,35 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import { withConfirmLeave } from 'core/decorators/confirm-leave';
import PkiTidyForm from 'vault/forms/secrets/pki/tidy';
@withConfirmLeave()
export default class PkiTidyManualRoute extends Route {
@service store;
@service secretMountPath;
model() {
return this.store.createRecord('pki/tidy', { backend: this.secretMountPath.currentPath });
return new PkiTidyForm(
'PkiTidyRequest',
{
acme_account_safety_buffer: 2592000,
issuer_safety_buffer: 31536000,
revocation_queue_safety_buffer: 172800,
safety_buffer: 259200,
tidy_acme: false,
tidy_revocation_queue: false,
},
{ isNew: true }
);
}
setupController(controller, resolvedModel) {
super.setupController(controller, resolvedModel);
const { currentPath } = this.secretMountPath;
controller.breadcrumbs = [
{ label: 'Secrets', route: 'secrets', linkExternal: true },
{ label: this.secretMountPath.currentPath, route: 'overview', model: resolvedModel.backend },
{ label: 'Configuration', route: 'configuration.index', model: resolvedModel.backend },
{ label: 'Tidy', route: 'tidy', model: resolvedModel.backend },
{ label: currentPath, route: 'overview', model: currentPath },
{ label: 'Configuration', route: 'configuration.index', model: currentPath },
{ label: 'Tidy', route: 'tidy', model: currentPath },
{ label: 'Manual' },
];
}

View file

@ -3,4 +3,4 @@
SPDX-License-Identifier: BUSL-1.1
}}
<Page::PkiTidyAutoConfigure @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />
<Page::PkiTidyAutoConfigure @form={{this.model}} @breadcrumbs={{this.breadcrumbs}} />

View file

@ -3,4 +3,4 @@
SPDX-License-Identifier: BUSL-1.1
}}
<Page::PkiTidyAutoSettings @model={{this.model}} @breadcrumbs={{this.breadcrumbs}} />
<Page::PkiTidyAutoSettings @model={{this.model}} @backend={{this.backend}} @breadcrumbs={{this.breadcrumbs}} />

View file

@ -6,7 +6,7 @@
<PkiPageHeader @backend={{this.model.engine}} />
{{#if this.model.hasConfig}}
<Page::PkiTidyStatus @autoTidyConfig={{this.model.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />
<Page::PkiTidyStatus @enabled={{this.model.autoTidyConfig.enabled}} @tidyStatus={{this.tidyStatus}} />
{{else}}
<Toolbar />
<EmptyState @title="PKI not configured" @message={{this.notConfiguredMessage}}>

View file

@ -48,7 +48,7 @@ module('Acceptance | pki tidy', function (hooks) {
.exists('Configure manual tidy button exists');
await click(PKI_TIDY.tidyConfigureModal.tidyModalManualButton);
assert.dom(PKI_TIDY_FORM.tidyFormName('manual')).exists('Manual tidy form exists');
await click(PKI_TIDY_FORM.inputByAttr('tidyCertStore'));
await click(PKI_TIDY_FORM.inputByAttr('tidy_cert_store'));
await fillIn(PKI_TIDY_FORM.tidyPauseDuration, '10');
await click(PKI_TIDY_FORM.tidySave);
await click(PKI_TIDY.cancelTidyAction);
@ -130,12 +130,12 @@ module('Acceptance | pki tidy', function (hooks) {
assert
.dom(PKI_TIDY_FORM.tidySectionHeader('ACME operations'))
.exists('Auto tidy form enabled shows ACME operations field');
await click(PKI_TIDY_FORM.inputByAttr('tidyCertStore'));
await click(PKI_TIDY_FORM.inputByAttr('tidy_cert_store'));
await click(PKI_TIDY_FORM.tidySave);
assert.strictEqual(currentRouteName(), 'vault.cluster.secrets.backend.pki.tidy.auto.index');
await click(PKI_TIDY_FORM.editAutoTidyButton);
assert.strictEqual(currentRouteName(), 'vault.cluster.secrets.backend.pki.tidy.auto.configure');
await click(PKI_TIDY_FORM.inputByAttr('tidyRevokedCerts'));
await click(PKI_TIDY_FORM.inputByAttr('tidy_revoked_certs'));
await click(PKI_TIDY_FORM.tidySave);
assert.strictEqual(currentRouteName(), 'vault.cluster.secrets.backend.pki.tidy.auto.index');
});
@ -151,7 +151,7 @@ module('Acceptance | pki tidy', function (hooks) {
await click(PKI_TIDY.tidyConfigureModal.tidyModalManualButton);
assert.dom(PKI_TIDY_FORM.tidyFormName('manual')).exists();
await click(PKI_TIDY_FORM.inputByAttr('tidyCertStore'));
await click(PKI_TIDY_FORM.inputByAttr('tidy_cert_store'));
await click(GENERAL.ttl.toggle('Tidy ACME disabled'));
assert
@ -190,8 +190,8 @@ module('Acceptance | pki tidy', function (hooks) {
assert.dom(PKI_TIDY.tidyConfigureModal.configureTidyModal).exists('Configure tidy modal exists');
await click(PKI_TIDY.tidyConfigureModal.tidyModalAutoButton);
await click(GENERAL.ttl.toggle('enabled'));
await click(PKI_TIDY_FORM.inputByAttr('tidyCertStore'));
await click(PKI_TIDY_FORM.inputByAttr('tidyRevokedCerts'));
await click(PKI_TIDY_FORM.inputByAttr('tidy_cert_store'));
await click(PKI_TIDY_FORM.inputByAttr('tidy_revoked_certs'));
await click(PKI_TIDY_FORM.tidySave);
await visit(`/vault/secrets-engines/${this.mountPath}/pki/tidy`);
assert

View file

@ -15,35 +15,32 @@ module('Integration | Component | page/pki-tidy-auto-settings', function (hooks)
setupEngine(hooks, 'pki');
hooks.beforeEach(function () {
const backend = 'pki-auto-tidy';
this.backend = backend;
this.context = { owner: this.engine };
this.store = this.owner.lookup('service:store');
this.backend = 'pki-auto-tidy';
this.breadcrumbs = [
{ label: 'Secrets', route: 'secrets', linkExternal: true },
{ label: backend, route: 'overview', model: backend },
{ label: 'Tidy', route: 'tidy.index', model: backend },
{ label: this.backend, route: 'overview', model: this.backend },
{ label: 'Tidy', route: 'tidy.index', model: this.backend },
{ label: 'Auto' },
];
this.model = {
enabled: false,
interval_duration: '2d',
tidy_cert_store: false,
tidy_expired_issuers: true,
};
this.renderComponent = () =>
render(
hbs`<Page::PkiTidyAutoSettings @breadcrumbs={{this.breadcrumbs}} @model={{this.model}} @backend={{this.backend}} />`,
{
owner: this.engine,
}
);
});
test('it renders', async function (assert) {
const model = this.store.createRecord('pki/tidy', {
backend: this.backend,
tidyType: 'auto',
enabled: false,
intervalDuration: '2d',
tidyCertStore: false,
tidyExpiredIssuers: true,
});
this.set('model', model);
await render(
hbs`<Page::PkiTidyAutoSettings @breadcrumbs={{this.breadcrumbs}} @model={{this.model}} />`,
this.context
);
await this.renderComponent();
assert.dom('[data-test-breadcrumbs] li').exists({ count: 4 }, 'an item exists for each breadcrumb');
assert.dom('[data-test-header-title]').hasText('Automatic Tidy Configuration', 'title is correct');

View file

@ -17,14 +17,10 @@ module('Integration | Component | Page::PkiTidyStatus', function (hooks) {
setupMirage(hooks);
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.secretMountPath = this.owner.lookup('service:secret-mount-path');
this.secretMountPath.currentPath = 'pki-test';
this.backend = 'pki-test';
this.secretMountPath = this.owner.lookup('service:secret-mount-path').update();
this.store.createRecord('pki/issuer', { issuerId: 'abcd-efgh' });
this.store.createRecord('pki/tidy', { backend: this.secretMountPath.currentPath, tidyType: 'auto' });
this.autoTidyConfig = this.store.peekAll('pki/tidy');
this.enabled = true;
this.tidyStatus = {
acme_account_deleted_count: 0,
acme_account_revoked_count: 0,
@ -54,60 +50,45 @@ module('Integration | Component | Page::PkiTidyStatus', function (hooks) {
time_started: '2023-05-18T13:27:36.390959-07:00',
};
this.engineId = 'pki';
this.renderComponent = () =>
render(hbs`<Page::PkiTidyStatus @enabled={{this.enabled}} @tidyStatus={{this.tidyStatus}} />`, {
owner: this.engine,
});
});
test('shows the correct titles for the alert banner based on states', async function (assert) {
await render(
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
{ owner: this.engine }
);
await this.renderComponent();
// running state
assert.dom(PKI_TIDY.hdsAlertTitle).hasText('Tidy in progress');
assert.dom(PKI_TIDY.cancelTidyAction).exists();
assert.dom(PKI_TIDY.hdsAlertButtonText).hasText('Cancel tidy');
// inactive state
this.tidyStatus.state = 'Inactive';
await render(
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
{ owner: this.engine }
);
await this.renderComponent();
assert.dom(PKI_TIDY.hdsAlertTitle).hasText('Tidy is inactive');
// finished state
this.tidyStatus.state = 'Finished';
await render(
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
{ owner: this.engine }
);
await this.renderComponent();
assert.dom(PKI_TIDY.hdsAlertTitle).hasText('Tidy operation finished');
// error state
this.tidyStatus.state = 'Error';
await render(
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
{ owner: this.engine }
);
await this.renderComponent();
assert.dom(PKI_TIDY.hdsAlertTitle).hasText('Tidy operation failed');
// cancelling state
this.tidyStatus.state = 'Cancelling';
await render(
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
{ owner: this.engine }
);
await this.renderComponent();
assert.dom(PKI_TIDY.hdsAlertTitle).hasText('Tidy operation cancelling');
// cancelled state
this.tidyStatus.state = 'Cancelled';
await render(
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
{ owner: this.engine }
);
await this.renderComponent();
assert.dom(PKI_TIDY.hdsAlertTitle).hasText('Tidy operation cancelled');
});
test('shows the fields even if the data returns null values', async function (assert) {
this.tidyStatus.time_started = null;
this.tidyStatus.time_finished = null;
await render(
hbs`<Page::PkiTidyStatus @autoTidyConfig={{this.autoTidyConfig}} @tidyStatus={{this.tidyStatus}} />`,
{ owner: this.engine }
);
await this.renderComponent();
assert.dom(PKI_TIDY.timeStartedRow).exists();
assert.dom(PKI_TIDY.timeFinishedRow).exists();
});

View file

@ -8,41 +8,60 @@ import { setupRenderingTest } from 'ember-qunit';
import { click, render, fillIn } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { setupEngine } from 'ember-engines/test-support';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { PKI_TIDY_FORM } from 'vault/tests/helpers/pki/pki-selectors';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import { convertToSeconds } from 'core/utils/duration-utils';
import PkiTidyForm from 'vault/forms/secrets/pki/tidy';
import TidyGroupsHelper from 'pki/helpers/tidy-groups';
import tidyFieldLabel from 'pki/helpers/tidy-field-label';
import sinon from 'sinon';
module('Integration | Component | pki tidy form', function (hooks) {
setupRenderingTest(hooks);
setupEngine(hooks, 'pki');
setupMirage(hooks);
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
this.backend = 'pki-test';
this.owner.lookup('service:secret-mount-path').update(this.backend);
this.version = this.owner.lookup('service:version');
this.version.type = 'enterprise';
this.server.post('/sys/capabilities-self', () => {});
this.onSave = () => {};
this.onCancel = () => {};
this.manualTidy = this.store.createRecord('pki/tidy', { backend: 'pki-manual-tidy' });
this.tidyGroupsHelper = new TidyGroupsHelper(this.owner);
const groupsReducer = (fields, group) => [...fields, ...Object.values(group)[0]];
this.allFields = this.tidyGroupsHelper.compute([]).reduce(groupsReducer, []);
this.autoFields = this.tidyGroupsHelper.compute(['auto']).reduce(groupsReducer, []);
this.onSave = sinon.spy();
this.onCancel = sinon.spy();
const { secrets } = this.owner.lookup('service:api');
this.tidyStub = sinon.stub(secrets, 'pkiTidy').resolves();
this.autoTidyStub = sinon.stub(secrets, 'pkiConfigureAutoTidy').resolves();
// setting defaults here to simulate how this form works in the app.
// on init, we retrieve these from the server and pre-populate form
this.autoTidyServerDefaults = {
enabled: false,
acme_account_safety_buffer: '48h',
interval_duration: '12h',
safety_buffer: '3d',
issuer_safety_buffer: '365d',
min_startup_backoff_duration: '5m',
max_startup_backoff_duration: '15m',
revocation_queue_safety_buffer: '10s',
pause_duration: '0',
};
this.renderComponent = (type = 'auto') => {
this.tidyType = type;
const formData = type === 'auto' ? this.autoTidyServerDefaults : {};
const schemaKey = type === 'auto' ? 'PkiConfigureAutoTidyRequest' : 'PkiTidyRequest';
this.form = new PkiTidyForm(schemaKey, formData, { isNew: type === 'manual' });
return render(
hbs`<PkiTidyForm @form={{this.form}} @tidyType={{this.tidyType}} @onSave={{this.onSave}} @onCancel={{this.onCancel}} />`,
{ owner: this.engine }
);
};
this.store.pushPayload('pki/tidy', {
modelName: 'pki/tidy',
id: 'pki-auto-tidy',
// setting defaults here to simulate how this form works in the app.
// on init, we retrieve these from the server and pre-populate form (instead of explicitly set on the model)
...this.autoTidyServerDefaults,
});
this.autoTidy = this.store.peekRecord('pki/tidy', 'pki-auto-tidy');
this.numTidyAttrs = Object.keys(this.autoTidy.allByKey).length;
});
test('it hides or shows fields depending on auto-tidy toggle', async function (assert) {
@ -53,31 +72,24 @@ module('Integration | Component | pki tidy form', function (hooks) {
'Issuer operations',
'Cross-cluster operations',
];
const loopAssertCount = this.numTidyAttrs * 2 - 3; // loop skips 3 params
const headerAssertCount = sectionHeaders.length * 2;
await this.renderComponent();
// the form isn't created until we render so expect assertions need to be after render
const loopAssertCount = this.allFields.length * 2 - 3; // loop skips 3 params
assert.expect(loopAssertCount + headerAssertCount + 4);
await render(
hbs`
<PkiTidyForm
@tidy={{this.autoTidy}}
@tidyType="auto"
@onSave={{this.onSave}}
@onCancel={{this.onCancel}}
/>
`,
{ owner: this.engine }
);
assert.dom(GENERAL.toggleInput('enabled')).isNotChecked();
assert
.dom(GENERAL.ttl.toggle('enabled'))
.hasText('Automatic tidy disabled Automatic tidy operations will not run.');
this.autoTidy.eachAttribute((attr) => {
if (attr === 'enabled') return;
assert
.dom(PKI_TIDY_FORM.inputByAttr(attr))
.doesNotExist(`does not render ${attr} when auto tidy disabled`);
this.allFields.forEach((field) => {
if (field !== 'enabled') {
assert
.dom(PKI_TIDY_FORM.inputByAttr(field))
.doesNotExist(`does not render ${field} when auto tidy disabled`);
}
});
sectionHeaders.forEach((group) => {
@ -89,10 +101,12 @@ module('Integration | Component | pki tidy form', function (hooks) {
assert.dom(GENERAL.toggleInput('enabled')).isChecked();
assert.dom(GENERAL.ttl.toggle('enabled')).hasText('Automatic tidy enabled');
this.autoTidy.eachAttribute((attr) => {
const skipFields = ['enabled', 'tidyAcme'];
if (skipFields.includes(attr)) return; // combined with duration ttl or asserted elsewhere
assert.dom(PKI_TIDY_FORM.inputByAttr(attr)).exists(`renders ${attr} when auto tidy enabled`);
this.allFields.forEach((field) => {
const skipFields = ['enabled', 'tidy_acme'];
// combined with duration ttl or asserted elsewhere
if (!skipFields.includes(field)) {
assert.dom(PKI_TIDY_FORM.inputByAttr(field)).exists(`renders ${field} when auto tidy enabled`);
}
});
sectionHeaders.forEach((group) => {
@ -102,74 +116,49 @@ module('Integration | Component | pki tidy form', function (hooks) {
test('it renders all attribute fields, including enterprise', async function (assert) {
assert.expect(35);
this.autoTidy.enabled = true;
const skipFields = ['enabled', 'tidyAcme']; // combined with duration ttl or asserted separately
await render(
hbs`
<PkiTidyForm
@tidy={{this.autoTidy}}
@tidyType="auto"
@onSave={{this.onSave}}
@onCancel={{this.onCancel}}
/>
`,
{ owner: this.engine }
);
this.autoTidy.eachAttribute((attr) => {
if (skipFields.includes(attr)) return;
assert.dom(PKI_TIDY_FORM.inputByAttr(attr)).exists(`renders ${attr} for auto tidyType`);
await this.renderComponent();
await click(GENERAL.toggleInput('enabled'));
const skipFields = ['enabled', 'tidy_acme']; // combined with duration ttl or asserted separately
this.allFields.forEach((field) => {
if (!skipFields.includes(field)) {
assert.dom(PKI_TIDY_FORM.inputByAttr(field)).exists(`renders ${field} for auto tidyType`);
}
});
// MANUAL TIDY
await render(
hbs`
<PkiTidyForm
@tidy={{this.manualTidy}}
@tidyType="manual"
@onSave={{this.onSave}}
@onCancel={{this.onCancel}}
/>
`,
{ owner: this.engine }
);
await this.renderComponent('manual');
assert.dom(GENERAL.toggleInput('enabled')).doesNotExist('hides automatic tidy toggle');
this.manualTidy.eachAttribute((attr) => {
if (skipFields.includes(attr)) return;
// auto tidy fields we shouldn't see in the manual tidy form
if (this.manualTidy.autoTidyConfigFields.includes(attr)) {
assert
.dom(PKI_TIDY_FORM.inputByAttr(attr))
.doesNotExist(`${attr} should not appear on manual tidyType`);
} else {
assert.dom(PKI_TIDY_FORM.inputByAttr(attr)).exists(`renders ${attr} for manual tidyType`);
this.allFields.forEach((field) => {
if (!skipFields.includes(field)) {
// auto tidy fields we shouldn't see in the manual tidy form
if (this.autoFields.includes(field)) {
assert
.dom(PKI_TIDY_FORM.inputByAttr(field))
.doesNotExist(`${field} should not appear on manual tidyType`);
} else {
assert.dom(PKI_TIDY_FORM.inputByAttr(field)).exists(`renders ${field} for manual tidyType`);
}
}
});
});
test('it hides enterprise fields for CE', async function (assert) {
this.version.type = 'community';
this.autoTidy.enabled = true;
const enterpriseFields = [
'tidyRevocationQueue',
'tidyCrossClusterRevokedCerts',
'revocationQueueSafetyBuffer',
'tidy_revocation_queue',
'tidy_cross_cluster_revoked_certs',
'tidy_cmpv2_nonce_store',
'revocation_queue_safety_buffer',
];
// tidyType = auto
await render(
hbs`
<PkiTidyForm
@tidy={{this.autoTidy}}
@tidyType="auto"
@onSave={{this.onSave}}
@onCancel={{this.onCancel}}
/>
`,
{ owner: this.engine }
);
await this.renderComponent();
await click(GENERAL.toggleInput('enabled'));
assert
.dom(PKI_TIDY_FORM.tidySectionHeader('Cross-cluster operations'))
@ -182,17 +171,7 @@ module('Integration | Component | pki tidy form', function (hooks) {
});
// tidyType = manual
await render(
hbs`
<PkiTidyForm
@tidy={{this.manualTidy}}
@tidyType="manual"
@onSave={{this.onSave}}
@onCancel={{this.onCancel}}
/>
`,
{ owner: this.engine }
);
await this.renderComponent('manual');
enterpriseFields.forEach((entAttr) => {
assert
@ -201,160 +180,111 @@ module('Integration | Component | pki tidy form', function (hooks) {
});
});
test('it should change the attributes on the model', async function (assert) {
assert.expect(12);
test('it should update form values', async function (assert) {
assert.expect(11);
// ttl picker defaults to seconds, unless unit is set by default value (set in beforeEach hook)
// on submit, any user inputted values should be converted to seconds for the payload
const fillInValues = {
acmeAccountSafetyBuffer: { time: 680, unit: 'h' },
intervalDuration: { time: 10, unit: 'h' },
issuerSafetyBuffer: { time: 20, unit: 'd' },
maxStartupBackoffDuration: { time: 30, unit: 'm' },
minStartupBackoffDuration: { time: 10, unit: 'm' },
pauseDuration: { time: 30, unit: 's' },
revocationQueueSafetyBuffer: { time: 40, unit: 's' },
safetyBuffer: { time: 50, unit: 'd' },
acme_account_safety_buffer: { time: 680, unit: 'h' },
interval_duration: { time: 10, unit: 'h' },
issuer_safety_buffer: { time: 20, unit: 'd' },
max_startup_backoff_duration: { time: 30, unit: 'm' },
min_startup_backoff_duration: { time: 10, unit: 'm' },
pause_duration: { time: 30, unit: 's' },
revocation_queue_safety_buffer: { time: 40, unit: 's' },
safety_buffer: { time: 50, unit: 'd' },
};
const calcValue = (param) => {
const { time, unit } = fillInValues[param];
return `${convertToSeconds(time, unit)}s`;
};
this.server.post('/pki-auto-tidy/config/auto-tidy', (schema, req) => {
assert.propEqual(
JSON.parse(req.requestBody),
{
acme_account_safety_buffer: '48h',
enabled: true,
min_startup_backoff_duration: calcValue('minStartupBackoffDuration'),
max_startup_backoff_duration: calcValue('maxStartupBackoffDuration'),
interval_duration: calcValue('intervalDuration'),
issuer_safety_buffer: calcValue('issuerSafetyBuffer'),
pause_duration: calcValue('pauseDuration'),
revocation_queue_safety_buffer: calcValue('revocationQueueSafetyBuffer'),
safety_buffer: calcValue('safetyBuffer'),
tidy_acme: true,
tidy_cert_metadata: true,
tidy_cert_store: true,
tidy_cmpv2_nonce_store: true,
tidy_cross_cluster_revoked_certs: true,
tidy_expired_issuers: true,
tidy_move_legacy_ca_bundle: true,
tidy_revocation_queue: true,
tidy_revoked_cert_issuer_associations: true,
tidy_revoked_certs: true,
},
'response contains updated model values'
);
});
await render(
hbs`
<PkiTidyForm
@tidy={{this.autoTidy}}
@tidyType="auto"
@onSave={{this.onSave}}
@onCancel={{this.onCancel}}
/>
`,
{ owner: this.engine }
);
await this.renderComponent();
assert.dom(GENERAL.toggleInput('enabled')).isNotChecked();
assert.dom(GENERAL.ttl.toggle('enabled')).hasTextContaining('Automatic tidy disabled');
assert.false(this.autoTidy.enabled, 'enabled is false on model');
assert.false(this.form.data.enabled, 'enabled is false on form');
// enable auto-tidy
await click(GENERAL.toggleInput('enabled'));
assert.dom(GENERAL.toggleInput('enabled')).isChecked();
assert.dom(GENERAL.ttl.toggle('enabled')).hasText('Automatic tidy enabled');
assert.dom(PKI_TIDY_FORM.toggleInput('acmeAccountSafetyBuffer')).isNotChecked('ACME tidy is disabled');
assert.dom(PKI_TIDY_FORM.toggleInput('acme_account_safety_buffer')).isNotChecked('ACME tidy is disabled');
assert
.dom(PKI_TIDY_FORM.toggleLabel('Tidy ACME disabled'))
.exists('ACME label has correct disabled text');
assert.false(this.autoTidy.tidyAcme, 'tidyAcme is false on model');
await click(PKI_TIDY_FORM.toggleInput('acmeAccountSafetyBuffer'));
await click(PKI_TIDY_FORM.toggleInput('acme_account_safety_buffer'));
await fillIn(PKI_TIDY_FORM.acmeAccountSafetyBuffer, 2); // units are days based on defaultValue
assert.dom(PKI_TIDY_FORM.toggleInput('acmeAccountSafetyBuffer')).isChecked('ACME tidy is enabled');
assert.dom(PKI_TIDY_FORM.toggleInput('acme_account_safety_buffer')).isChecked('ACME tidy is enabled');
assert.dom(PKI_TIDY_FORM.toggleLabel('Tidy ACME enabled')).exists('ACME label has correct enabled text');
assert.true(this.autoTidy.tidyAcme, 'tidyAcme toggles to true');
assert.true(this.form.data.tidy_acme, 'tidy_acme toggles to true');
this.autoTidy.eachAttribute(async (attr, { type }) => {
const skipFields = ['enabled', 'tidyAcme', 'acmeAccountSafetyBuffer']; // combined with duration ttl or asserted separately
if (skipFields.includes(attr)) return;
// all params right now are either a boolean or TTL, this if/else will need to be updated if that changes
if (type === 'boolean') {
await click(PKI_TIDY_FORM.inputByAttr(attr));
} else {
const { time } = fillInValues[attr];
await fillIn(PKI_TIDY_FORM.toggleInput(attr), `${time}`);
for (const field of this.allFields) {
const skipFields = ['enabled', 'tidy_acme', 'acme_account_safety_buffer']; // combined with duration ttl or asserted separately
if (!skipFields.includes(field)) {
// all params right now are either a boolean or TTL, this if/else will need to be updated if that changes
if (Object.keys(fillInValues).includes(field)) {
const { time } = fillInValues[field];
await fillIn(GENERAL.ttl.input(tidyFieldLabel(field)), `${time}`);
} else {
await click(PKI_TIDY_FORM.inputByAttr(field));
}
}
});
}
await click(PKI_TIDY_FORM.tidySave);
const payload = {
acme_account_safety_buffer: '48h',
enabled: true,
min_startup_backoff_duration: calcValue('min_startup_backoff_duration'),
max_startup_backoff_duration: calcValue('max_startup_backoff_duration'),
interval_duration: calcValue('interval_duration'),
issuer_safety_buffer: calcValue('issuer_safety_buffer'),
pause_duration: calcValue('pause_duration'),
revocation_queue_safety_buffer: calcValue('revocation_queue_safety_buffer'),
safety_buffer: calcValue('safety_buffer'),
tidy_acme: true,
tidy_cert_metadata: true,
tidy_cert_store: true,
tidy_cmpv2_nonce_store: true,
tidy_cross_cluster_revoked_certs: true,
tidy_expired_issuers: true,
tidy_move_legacy_ca_bundle: true,
tidy_revocation_queue: true,
tidy_revoked_cert_issuer_associations: true,
tidy_revoked_certs: true,
};
assert.true(this.autoTidyStub.calledWith(this.backend, payload), 'API called with correct payload');
});
test('it updates auto-tidy config', async function (assert) {
assert.expect(4);
this.server.post('/pki-auto-tidy/config/auto-tidy', (schema, req) => {
assert.ok(true, 'Request made to update auto-tidy');
assert.propEqual(
JSON.parse(req.requestBody),
{
...this.autoTidyServerDefaults,
acme_account_safety_buffer: '720h',
tidy_acme: false,
},
'response contains default auto-tidy params'
);
});
this.onSave = () => assert.ok(true, 'onSave callback fires on save success');
this.onCancel = () => assert.ok(true, 'onCancel callback fires on save success');
assert.expect(3);
await render(
hbs`
<PkiTidyForm
@tidy={{this.autoTidy}}
@tidyType="auto"
@onSave={{this.onSave}}
@onCancel={{this.onCancel}}
/>
`,
{ owner: this.engine }
);
await this.renderComponent();
await click(PKI_TIDY_FORM.tidySave);
assert.true(
this.autoTidyStub.calledWith(this.backend, this.autoTidyServerDefaults),
'API called with correct payload'
);
assert.true(this.onSave.called, 'onSave called on save success');
await click(PKI_TIDY_FORM.tidyCancel);
assert.true(this.onCancel.called, 'onCancel called on click');
});
test('it saves and performs manual tidy', async function (assert) {
assert.expect(4);
assert.expect(3);
this.server.post('/pki-manual-tidy/tidy', (schema, req) => {
assert.ok(true, 'Request made to perform manual tidy');
assert.propEqual(
JSON.parse(req.requestBody),
{ acme_account_safety_buffer: '720h', tidy_acme: false },
'response contains manual tidy params'
);
return { id: 'pki-manual-tidy' };
});
this.onSave = () => assert.ok(true, 'onSave callback fires on save success');
this.onCancel = () => assert.ok(true, 'onCancel callback fires on save success');
await render(
hbs`
<PkiTidyForm
@tidy={{this.manualTidy}}
@tidyType="manual"
@onSave={{this.onSave}}
@onCancel={{this.onCancel}}
/>
`,
{ owner: this.engine }
);
await this.renderComponent('manual');
await click(PKI_TIDY_FORM.tidySave);
assert.true(this.tidyStub.calledWith(this.backend), 'API called with correct payload');
assert.true(this.onSave.called, 'onSave called on save success');
await click(PKI_TIDY_FORM.tidyCancel);
assert.true(this.onCancel.called, 'onCancel called on click');
});
});