fix(editor): Avoid clearing EULA license key when closing dialog (#22424)

Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
This commit is contained in:
Marc Littlemore 2025-11-28 09:20:06 +00:00 committed by GitHub
parent a4d2cfae4a
commit fe05ea4df8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 108 additions and 7 deletions

View File

@ -257,6 +257,63 @@ describe('SettingsUsageAndPlan', () => {
expect(usageStore.activateLicense).toHaveBeenCalledTimes(1);
});
it('should preserve activation key during EULA flow and send it with acceptance', async () => {
usageStore.isLoading = false;
usageStore.planName = 'Community';
usersStore.currentUser = {
globalScopes: ['license:manage'],
} as IUser;
rbacStore.setGlobalScopes(['license:manage']);
usageStore.activateLicense
.mockRejectedValueOnce({
httpStatusCode: 400,
meta: { eulaUrl: 'https://example.com/eula.pdf' },
})
.mockResolvedValueOnce(undefined);
const { getByRole, findByTestId } = renderComponent();
// Open activation modal and enter key
await userEvent.click(getByRole('button', { name: /activation/i }));
const input = document.querySelector('input') as HTMLInputElement;
await userEvent.type(input, 'test-key-123');
// Click activate - this should trigger EULA modal
await userEvent.click(getByRole('button', { name: /activate/i }));
// EULA modal should appear
const eulaModal = await findByTestId('eula-acceptance-modal');
expect(eulaModal).toBeInTheDocument();
// Accept EULA
const checkbox = (await findByTestId('eula-checkbox')).querySelector(
'input[type="checkbox"]',
) as HTMLInputElement;
await userEvent.click(checkbox);
const acceptButton = await findByTestId('eula-accept-button');
await userEvent.click(acceptButton);
// Verify the activation key was preserved and sent with EULA acceptance
await waitFor(() => {
expect(usageStore.activateLicense).toHaveBeenCalledTimes(2);
// First call without EULA
expect(usageStore.activateLicense).toHaveBeenNthCalledWith(1, 'test-key-123', undefined);
// Second call with EULA URL - key should still be present
expect(usageStore.activateLicense).toHaveBeenNthCalledWith(
2,
'test-key-123',
'https://example.com/eula.pdf',
);
});
expect(mockToast.showMessage).toHaveBeenCalledWith(
expect.objectContaining({
type: 'success',
}),
);
});
it('should show error when activation fails without EULA requirement', async () => {
usageStore.isLoading = false;
usageStore.planName = 'Community';
@ -424,5 +481,41 @@ describe('SettingsUsageAndPlan', () => {
expect(reopenedInput).toBeInTheDocument();
});
});
it('should clear activation key when cancel button is clicked', async () => {
usageStore.isLoading = false;
usersStore.currentUser = {
globalScopes: ['license:manage'],
} as IUser;
rbacStore.setGlobalScopes(['license:manage']);
const { getByRole, findByPlaceholderText } = renderComponent();
await waitAllPromises();
// Open activation modal
await userEvent.click(getByRole('button', { name: /activation/i }));
const input = await findByPlaceholderText('Activation key');
expect(input).toBeInTheDocument();
// Enter activation key
await userEvent.type(input, 'test-key-should-be-cleared');
expect(input).toHaveValue('test-key-should-be-cleared');
// Click cancel
const cancelButton = getByRole('button', { name: /cancel/i });
await userEvent.click(cancelButton);
await waitAllPromises();
// Reopen modal
await userEvent.click(getByRole('button', { name: /activation/i }));
// Input should be cleared
await waitFor(async () => {
const reopenedInput = await findByPlaceholderText('Activation key');
expect(reopenedInput).toHaveValue('');
});
});
});
});

View File

@ -111,9 +111,9 @@ const onLicenseActivation = async (eulaUri?: string) => {
} catch (error: unknown) {
// Check if error requires EULA acceptance using type guard
if (isEulaError(error)) {
activationKeyModal.value = false;
eulaUrl.value = error.meta.eulaUrl;
eulaModal.value = true;
activationKeyModal.value = false;
return;
}
@ -138,6 +138,18 @@ const onEulaCancel = () => {
activationKey.value = '';
};
const onActivationCancel = () => {
activationKeyModal.value = false;
activationKey.value = '';
};
const onActivationModalClose = () => {
// Only clear key if not transitioning to EULA flow
if (!eulaModal.value) {
onActivationCancel();
}
};
onMounted(async () => {
documentTitle.set(locale.baseText('settings.usageAndPlan.title'));
usageStore.setLoading(true);
@ -187,10 +199,6 @@ const onManagePlan = () => {
sendUsageTelemetry('manage_plan');
};
const onDialogClosed = () => {
activationKey.value = '';
};
const onDialogOpened = () => {
activationKeyInput.value?.focus();
};
@ -307,7 +315,7 @@ const openCommunityRegisterModal = () => {
top="0"
:title="locale.baseText('settings.usageAndPlan.dialog.activation.title')"
:modal-class="$style.center"
@closed="onDialogClosed"
@closed="onActivationModalClose"
@opened="onDialogOpened"
>
<template #default>
@ -318,7 +326,7 @@ const openCommunityRegisterModal = () => {
/>
</template>
<template #footer>
<N8nButton type="secondary" @click="activationKeyModal = false">
<N8nButton type="secondary" @click="onActivationCancel">
{{ locale.baseText('settings.usageAndPlan.dialog.activation.cancel') }}
</N8nButton>
<N8nButton :disabled="!activationKey" @click="() => onLicenseActivation()">