diff --git a/web/__test__/components/Onboarding/OnboardingModal.test.ts b/web/__test__/components/Onboarding/OnboardingModal.test.ts index 9fb9b4e73d..74d824fdd6 100644 --- a/web/__test__/components/Onboarding/OnboardingModal.test.ts +++ b/web/__test__/components/Onboarding/OnboardingModal.test.ts @@ -191,15 +191,6 @@ describe('OnboardingModal.vue', () => { enableBootTransfer: 'yes', }, }; - - Object.defineProperty(window, 'location', { - writable: true, - configurable: true, - value: { - href: '', - pathname: '/Dashboard', - }, - }); }); const mountComponent = () => { @@ -302,24 +293,6 @@ describe('OnboardingModal.vue', () => { expect(wrapper.find('[data-testid="dialog"]').exists()).toBe(false); }); - it('does not render on login route', () => { - Object.defineProperty(window, 'location', { - writable: true, - configurable: true, - value: { - href: '', - pathname: '/login', - }, - }); - - onboardingModalStoreState.isAutoVisible.value = true; - onboardingStatusStore.canDisplayOnboardingModal.value = true; - - const wrapper = mountComponent(); - - expect(wrapper.find('[data-testid="dialog"]').exists()).toBe(false); - }); - it('shows activation step for ENOKEYFILE1', () => { activationCodeDataStore.registrationState.value = 'ENOKEYFILE1'; onboardingDraftStore.currentStepIndex.value = 4; diff --git a/web/__test__/components/Registration.test.ts b/web/__test__/components/Registration.test.ts index a2ff2bde2e..5196d0c872 100644 --- a/web/__test__/components/Registration.test.ts +++ b/web/__test__/components/Registration.test.ts @@ -13,6 +13,7 @@ import type { ServerconnectPluginInstalled } from '~/types/server'; import type { Pinia } from 'pinia'; import Registration from '~/components/Registration.standalone.vue'; +import { useAccountStore } from '~/store/account'; import { usePurchaseStore } from '~/store/purchase'; import { useReplaceRenewStore } from '~/store/replaceRenew'; import { useServerStore } from '~/store/server'; @@ -151,6 +152,7 @@ const t = testTranslate; describe('Registration.standalone.vue', () => { let wrapper: VueWrapper; let pinia: Pinia; + let accountStore: ReturnType; let serverStore: ReturnType; let replaceRenewStore: ReturnType; let purchaseStore: ReturnType; @@ -184,6 +186,7 @@ describe('Registration.standalone.vue', () => { }); setActivePinia(pinia); + accountStore = useAccountStore(); serverStore = useServerStore(); replaceRenewStore = useReplaceRenewStore(); purchaseStore = usePurchaseStore(); @@ -306,47 +309,34 @@ describe('Registration.standalone.vue', () => { expect(attachedStorageDevicesItem?.props('text')).toBe('8 out of unlimited devices'); }); - it('shows TPM transfer guidance when TPM licensing is available', async () => { + it('shows Move License to TPM when TPM licensing is available', async () => { serverStore.state = 'PRO'; serverStore.guid = '058F-6387-0000-0000F1F1E1C6'; serverStore.flashGuid = '058F-6387-0000-0000F1F1E1C6'; - serverStore.mdState = 'STOPPED'; serverStore.tpmGuid = '03-V35H8S0L1QHK1SBG1XHXJNH7'; serverStore.keyfile = 'keyfile-present'; await wrapper.vm.$nextTick(); - const transferNotice = wrapper.find('[data-testid="tpm-transfer-available"]'); + const moveButton = wrapper.find('[data-testid="move-license-to-tpm"]'); - expect(transferNotice.exists()).toBe(true); - expect(transferNotice.text()).toContain('TPM licensing is available on this server.'); - expect(transferNotice.text()).toContain('Stop the array.'); - expect(transferNotice.text()).toContain('Remove the USB flash boot device.'); - expect(transferNotice.text()).toContain('Refresh this page.'); - expect(transferNotice.text()).toContain('Press Replace Key.'); - expect(transferNotice.text()).toContain('Start the array.'); - expect(transferNotice.text()).not.toContain('Tools > Registration'); + expect(moveButton.exists()).toBe(true); }); - it('only checks the stop-array step when the array is stopped', async () => { + it('shows Move License to TPM when flashGuid is missing but the active GUID is still a flash GUID', async () => { serverStore.state = 'PRO'; serverStore.guid = '058F-6387-0000-0000F1F1E1C6'; - serverStore.flashGuid = '058F-6387-0000-0000F1F1E1C6'; - serverStore.mdState = 'STARTED'; + serverStore.flashGuid = ''; serverStore.tpmGuid = '03-V35H8S0L1QHK1SBG1XHXJNH7'; serverStore.keyfile = 'keyfile-present'; await wrapper.vm.$nextTick(); - const transferNotice = wrapper.find('[data-testid="tpm-transfer-available"]'); - - expect(transferNotice.exists()).toBe(true); - expect(transferNotice.text()).not.toContain('[x] Stop the array.'); - expect(transferNotice.text()).toMatch(/\[\s\]Stop the array\./); + expect(wrapper.find('[data-testid="move-license-to-tpm"]').exists()).toBe(true); }); - it('shows TPM purchase guidance instead of TPM transfer steps for trial states', async () => { - serverStore.state = 'TRIAL'; + it('triggers the TPM replacement action when Move License to TPM is clicked', async () => { + serverStore.state = 'PRO'; serverStore.guid = '058F-6387-0000-0000F1F1E1C6'; serverStore.flashGuid = '058F-6387-0000-0000F1F1E1C6'; serverStore.tpmGuid = '03-V35H8S0L1QHK1SBG1XHXJNH7'; @@ -354,56 +344,32 @@ describe('Registration.standalone.vue', () => { await wrapper.vm.$nextTick(); - const trialNotice = wrapper.find('[data-testid="tpm-transfer-trial"]'); + await wrapper.find('[data-testid="move-license-to-tpm"]').trigger('click'); - expect(trialNotice.exists()).toBe(true); - expect(trialNotice.text()).toContain( - 'TPM licensing will be available after you purchase a license.' - ); - expect(trialNotice.text()).toContain( - 'Trial licenses cannot be moved to TPM. Once you purchase a license for this server, you will be able to transfer it from your USB flash device to TPM.' - ); - expect(wrapper.find('[data-testid="tpm-transfer-available"]').exists()).toBe(false); + expect(accountStore.replaceTpm).toHaveBeenCalled(); }); - it('shows checked TPM transfer steps after switching to TPM boot', async () => { - serverStore.state = 'EGUID'; - serverStore.guid = '03-V35H8S0L1QHK1SBG1XHXJNH7'; - serverStore.mdState = 'STOPPED'; + it('does not show Move License to TPM for trial states', async () => { + serverStore.state = 'TRIAL'; + serverStore.guid = '058F-6387-0000-0000F1F1E1C6'; + serverStore.flashGuid = '058F-6387-0000-0000F1F1E1C6'; serverStore.tpmGuid = '03-V35H8S0L1QHK1SBG1XHXJNH7'; - serverStore.regGuid = '058F-6387-0000-0000F1F1E1C6'; + serverStore.keyfile = 'keyfile-present'; await wrapper.vm.$nextTick(); - const transferNotice = wrapper.find('[data-testid="tpm-transfer-ready"]'); - - expect(transferNotice.exists()).toBe(true); - expect(transferNotice.text()).toContain('Continue your TPM license transfer.'); - expect(transferNotice.text()).toContain('The first two steps are already complete.'); - expect(transferNotice.text()).toContain('[x]'); - expect(transferNotice.text()).toContain('Stop the array.'); - expect(transferNotice.text()).toContain('Remove the USB flash boot device.'); - expect(transferNotice.text()).toContain('Press Replace Key.'); - expect(transferNotice.text()).toContain('Start the array.'); + expect(wrapper.find('[data-testid="move-license-to-tpm"]').exists()).toBe(false); }); - it('shows the stop-array step as incomplete in TPM-ready state while the array is running', async () => { + it('does not show Move License to TPM after switching to TPM boot', async () => { serverStore.state = 'EGUID'; serverStore.guid = '03-V35H8S0L1QHK1SBG1XHXJNH7'; - serverStore.mdState = 'STARTED'; serverStore.tpmGuid = '03-V35H8S0L1QHK1SBG1XHXJNH7'; serverStore.regGuid = '058F-6387-0000-0000F1F1E1C6'; await wrapper.vm.$nextTick(); - const transferNotice = wrapper.find('[data-testid="tpm-transfer-ready"]'); - - expect(transferNotice.exists()).toBe(true); - expect(transferNotice.text()).toContain( - 'The USB flash boot device is already removed. Stop the array, then press Replace Key to transfer this license to TPM.' - ); - expect(transferNotice.text()).toMatch(/\[\s\]Stop the array\./); - expect(transferNotice.text()).toMatch(/\[x\]Remove the USB flash boot device\./); + expect(wrapper.find('[data-testid="move-license-to-tpm"]').exists()).toBe(false); }); it('adds Activate Trial fallback for ENOKEYFILE partner activation', async () => { diff --git a/web/__test__/store/account.test.ts b/web/__test__/store/account.test.ts index 33b3465a21..888d540911 100644 --- a/web/__test__/store/account.test.ts +++ b/web/__test__/store/account.test.ts @@ -67,6 +67,10 @@ vi.mock('~/store/server', () => ({ guid: 'test-guid', name: 'test-server', }, + serverReplacePayload: { + guid: 'test-tpm-guid', + name: 'test-server', + }, inIframe: false, }), })); @@ -218,6 +222,23 @@ describe('Account Store', () => { ); }); + it('should call replaceTpm action correctly', () => { + store.replaceTpm(); + + expect(mockSend).toHaveBeenCalledTimes(1); + expect(mockSend).toHaveBeenCalledWith( + ACCOUNT_CALLBACK.toString(), + [ + { + server: { guid: 'test-tpm-guid', name: 'test-server' }, + type: 'replace', + }, + ], + undefined, + 'post' + ); + }); + it('should call trialExtend action correctly', () => { store.trialExtend(); diff --git a/web/__test__/store/server.test.ts b/web/__test__/store/server.test.ts index 11d1fa2214..90a8177bc5 100644 --- a/web/__test__/store/server.test.ts +++ b/web/__test__/store/server.test.ts @@ -204,6 +204,15 @@ const getStore = () => { wanFQDN: store.wanFQDN, }), }, + serverReplacePayload: { + get: () => ({ + ...store.serverAccountPayload, + guid: + store.guid && !store.guid.startsWith('03-') && store.tpmGuid && store.guid !== store.tpmGuid + ? store.tpmGuid + : store.guid || undefined, + }), + }, }); // Mock store methods @@ -618,6 +627,7 @@ describe('useServerStore', () => { deviceCount: 6, description: 'Test Server', expireTime: 123, + flashGuid: 'flash-guid-1', flashProduct: 'TestFlash', flashVendor: 'TestVendor', guid: '123456', @@ -658,6 +668,59 @@ describe('useServerStore', () => { expect(payload.wanFQDN).toBe('test.myunraid.net'); }); + it('should create serverReplacePayload with TPM guid when available on flash boot', () => { + const store = getStore(); + + store.setServer({ + flashGuid: '058F-6387-0000-0000F1F1E1C6', + guid: '058F-6387-0000-0000F1F1E1C6', + keyfile: '/boot/config/Pro.key', + state: 'PRO' as ServerState, + tpmGuid: '03-V35H8S0L1QHK1SBG1XHXJNH7', + }); + + expect(store.serverReplacePayload.guid).toBe('03-V35H8S0L1QHK1SBG1XHXJNH7'); + }); + + it('should create serverReplacePayload with flash guid when TPM replacement is not available', () => { + const store = getStore(); + + store.setServer({ + flashGuid: '058F-6387-0000-0000F1F1E1C6', + guid: '058F-6387-0000-0000F1F1E1C6', + state: 'PRO' as ServerState, + tpmGuid: '058F-6387-0000-0000F1F1E1C6', + }); + + expect(store.serverReplacePayload.guid).toBe('058F-6387-0000-0000F1F1E1C6'); + }); + + it('should create serverReplacePayload with the active TPM guid when booted from TPM', () => { + const store = getStore(); + + store.setServer({ + flashGuid: '058F-6387-0000-0000F1F1E1C6', + guid: '03-V35H8S0L1QHK1SBG1XHXJNH7', + state: 'PRO' as ServerState, + tpmGuid: undefined, + }); + + expect(store.serverReplacePayload.guid).toBe('03-V35H8S0L1QHK1SBG1XHXJNH7'); + }); + + it('should create serverReplacePayload with the active flash guid when TPM guid is missing', () => { + const store = getStore(); + + store.setServer({ + flashGuid: '058F-6387-0000-0000F1F1E1C6', + guid: '058F-6387-0000-0000F1F1E1C6', + state: 'PRO' as ServerState, + tpmGuid: undefined, + }); + + expect(store.serverReplacePayload.guid).toBe('058F-6387-0000-0000F1F1E1C6'); + }); + it('should handle OS version ignore functionality', () => { const store = getStore(); store.setServer({ updateOsIgnoredReleases: [] }); diff --git a/web/src/components/Onboarding/OnboardingModal.vue b/web/src/components/Onboarding/OnboardingModal.vue index e601bb7fa7..e10ad79446 100644 --- a/web/src/components/Onboarding/OnboardingModal.vue +++ b/web/src/components/Onboarding/OnboardingModal.vue @@ -105,13 +105,8 @@ const availableSteps = computed(() => visibleHardcodedSteps.value.map( // Filtered steps as full objects for OnboardingSteps component const filteredSteps = computed(() => visibleHardcodedSteps.value); -const isLoginPage = computed(() => { - const hasLoginRoute = window.location.pathname.includes('login'); - const hasLoginMarkup = Boolean(document.querySelector('#login, form[action="/login"]')); - return hasLoginRoute || hasLoginMarkup; -}); const showModal = computed(() => { - if (isLoginPage.value || !canDisplayOnboardingModal.value) { + if (!canDisplayOnboardingModal.value) { return false; } diff --git a/web/src/components/Onboarding/standalone/OnboardingAdminPanel.standalone.vue b/web/src/components/Onboarding/standalone/OnboardingAdminPanel.standalone.vue index 03532ed9ee..b069da1b14 100644 --- a/web/src/components/Onboarding/standalone/OnboardingAdminPanel.standalone.vue +++ b/web/src/components/Onboarding/standalone/OnboardingAdminPanel.standalone.vue @@ -24,7 +24,6 @@ const { isPartnerBuild, completed, completedAtVersion, - mockUnauthenticated, osVersion, effectiveOsVersion, isVersionSupported, @@ -376,11 +375,6 @@ const openOnboardingModalFromPanel = () => { onboardingModalStore.forceOpenModal(); }; -const onMockUnauthenticatedChange = (event: Event) => { - const target = event.target as HTMLInputElement | null; - onboardingStore.setMockUnauthenticated(Boolean(target?.checked)); -}; - const getMutationInput = (payload: OnboardingOverridePayload) => { const mutationInput = { ...payload }; delete mutationInput.currentVersion; @@ -767,26 +761,6 @@ const currentRegistrationState = computed({ -
- -
-