zammad/zammad

View on GitHub
app/frontend/apps/desktop/pages/guided-setup/__tests__/guided-setup-import-source-status.spec.ts

Summary

Maintainability
C
1 day
Test Coverage
// Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/

import { mockApplicationConfig } from '#tests/support/mock-applicationConfig.ts'
import { visitView } from '#tests/support/components/visitView.ts'

import {
  EnumSystemSetupInfoStatus,
  EnumSystemSetupInfoType,
} from '#shared/graphql/types.ts'
import { useApplicationStore } from '#shared/stores/application.ts'
import { getConfigUpdatesSubscriptionHandler } from '#shared/graphql/subscriptions/configUpdates.mocks.ts'
import { waitForNextTick } from '#tests/support/utils.ts'

import { mockSystemSetupInfoQuery } from '../graphql/queries/systemSetupInfo.mocks.ts'
import { mockSystemSetupInfo } from './mocks/mock-systemSetupInfo.ts'
import { mockSystemImportStateQuery } from '../graphql/queries/systemImportState.mocks.ts'

describe('guided setup import source status', () => {
  describe('when import_mode is not set', () => {
    beforeEach(() => {
      mockApplicationConfig({
        import_backend: 'freshdesk',
        import_mode: false,
        system_init_done: false,
      })

      mockSystemSetupInfo({
        status: EnumSystemSetupInfoStatus.InProgress,
        type: EnumSystemSetupInfoType.Import,
        lockValue: 'random-uuid-lock',
        importSource: 'freshdesk',
      })

      mockSystemSetupInfoQuery({
        systemSetupInfo: {
          status: EnumSystemSetupInfoStatus.InProgress,
          type: EnumSystemSetupInfoType.Import,
        },
      })
    })

    it('redirects to freshdesk configuration', async () => {
      const view = await visitView('/guided-setup/import/freshdesk/status')

      await vi.waitFor(() => {
        expect(
          view,
          'correctly redirects to guided setup import source freshdesk configuration',
        ).toHaveCurrentUrl('/guided-setup/import/freshdesk')
      })
    })
  })

  describe('when import is started', () => {
    beforeEach(() => {
      mockApplicationConfig({
        system_init_done: false,
        import_mode: true,
        import_backend: 'freshdesk',
      })
      mockSystemSetupInfo({
        status: EnumSystemSetupInfoStatus.InProgress,
        type: EnumSystemSetupInfoType.Import,
        lockValue: 'random-uuid-lock',
        importSource: 'freshdesk',
      })
      mockSystemSetupInfoQuery({
        systemSetupInfo: {
          status: EnumSystemSetupInfoStatus.InProgress,
          type: EnumSystemSetupInfoType.Import,
        },
      })

      vi.useFakeTimers()
    })

    afterEach(() => {
      vi.useRealTimers()
    })

    it('shows the import starting message', async () => {
      const mockerImportState = mockSystemImportStateQuery({
        systemImportState: {
          result: null,
          finishedAt: null,
          startedAt: null,
        },
      })

      const view = await visitView('/guided-setup/import/freshdesk/status')

      const mockCalls = await mockerImportState.waitForCalls()
      expect(mockCalls).toHaveLength(1)

      expect(view.getByText('Freshdesk Import Status')).toBeInTheDocument()
      expect(view.getByText('Starting import…')).toBeInTheDocument()

      mockSystemImportStateQuery({
        systemImportState: {
          result: {},
          finishedAt: null,
          startedAt: new Date().toISOString(),
        },
      })

      vi.advanceTimersByTime(5000)
      expect(mockCalls).toHaveLength(2)

      await waitForNextTick()

      expect(view.queryByText('Starting import…')).not.toBeInTheDocument()

      expect(view.getByLabelText('Groups')).toBeInTheDocument()
      expect(view.getByLabelText('Organizations')).toBeInTheDocument()
      expect(view.getByLabelText('Users')).toBeInTheDocument()
      expect(view.getByLabelText('Tickets')).toBeInTheDocument()
    })

    it('shows the result and updated results', async () => {
      const startDate = new Date().toISOString()
      const mockerImportState = mockSystemImportStateQuery({
        systemImportState: {
          result: {},
          finishedAt: null,
          startedAt: startDate,
        },
      })

      const view = await visitView('/guided-setup/import/freshdesk/status')

      const mockCalls = await mockerImportState.waitForCalls()
      expect(mockCalls).toHaveLength(1)

      expect(view.getByLabelText('Groups')).toBeInTheDocument()
      expect(view.getByLabelText('Organizations')).toBeInTheDocument()
      expect(view.getByLabelText('Users')).toBeInTheDocument()
      expect(view.getByLabelText('Tickets')).toBeInTheDocument()

      mockSystemImportStateQuery({
        systemImportState: {
          result: {
            Groups: { sum: 5, total: 10 },
          },
          finishedAt: null,
          startedAt: startDate,
        },
      })

      await vi.advanceTimersByTimeAsync(5000)
      expect(mockCalls).toHaveLength(2)

      await waitForNextTick()

      const groupProgress = view.getByRole('progressbar', { name: 'Groups' })
      expect(groupProgress).toHaveAttribute('value', '5')
      expect(groupProgress).toHaveAttribute('max', '10')

      mockSystemImportStateQuery({
        systemImportState: {
          result: {
            Groups: { sum: 10, total: 10 },
            Organizations: { sum: 100, total: 100 },
            Users: { sum: 5000, total: 10000 },
          },
          finishedAt: null,
          startedAt: startDate,
        },
      })

      await vi.advanceTimersByTimeAsync(5000)
      expect(mockCalls).toHaveLength(3)

      await waitForNextTick()

      expect(groupProgress).toHaveAttribute('value', '10')
      expect(groupProgress).toHaveAttribute('max', '10')

      const organizationProgress = view.getByRole('progressbar', {
        name: 'Organizations',
      })
      expect(organizationProgress).toHaveAttribute('value', '100')
      expect(organizationProgress).toHaveAttribute('max', '100')

      const userProgress = view.getByRole('progressbar', {
        name: 'Users',
      })
      expect(userProgress).toHaveAttribute('value', '5000')
      expect(userProgress).toHaveAttribute('max', '10000')
    })

    it('shows directly the result and finish import with next refresh', async () => {
      const startDate = new Date().toISOString()
      const mockerImportState = mockSystemImportStateQuery({
        systemImportState: {
          result: {
            Groups: { sum: 10, total: 10 },
            Organizations: { sum: 100, total: 100 },
            Users: { sum: 10000, total: 10000 },
            Tickets: { sum: 80000, total: 100000 },
          },
          finishedAt: null,
          startedAt: startDate,
        },
      })

      const view = await visitView('/guided-setup/import/freshdesk/status')

      const application = useApplicationStore()
      application.initializeConfigUpdateSubscription()
      const configUpdateSubscription = getConfigUpdatesSubscriptionHandler()

      const mockCalls = await mockerImportState.waitForCalls()
      expect(mockCalls).toHaveLength(1)

      const ticketProgress = view.getByRole('progressbar', {
        name: 'Tickets',
      })
      expect(ticketProgress).toHaveAttribute('value', '80000')
      expect(ticketProgress).toHaveAttribute('max', '100000')

      mockSystemImportStateQuery({
        systemImportState: {
          result: {
            Groups: { sum: 10, total: 10 },
            Organizations: { sum: 100, total: 100 },
            Users: { sum: 10000, total: 10000 },
            Tickets: { sum: 100000, total: 100000 },
          },
          finishedAt: new Date().toISOString(),
          startedAt: startDate,
        },
      })

      await vi.advanceTimersByTimeAsync(5000)
      expect(mockCalls).toHaveLength(2)
      vi.useRealTimers()

      await configUpdateSubscription.trigger({
        configUpdates: {
          setting: {
            key: 'import_mode',
            value: false,
          },
        },
      })
      await configUpdateSubscription.trigger({
        configUpdates: {
          setting: {
            key: 'system_init_done',
            value: true,
          },
        },
      })

      await waitForNextTick()

      const successMessage = await view.findByText(
        'Import finished successfully!',
      )

      expect(successMessage.role).toBe('alert')
      expect(successMessage).toBeInTheDocument()

      const goToLoginButton = view.getByRole('button', {
        name: 'Go to Login',
      })
      await view.events.click(goToLoginButton)

      await vi.waitFor(() => {
        expect(view, 'correctly redirects to login page').toHaveCurrentUrl(
          '/login',
        )
      })
    })

    it('shows import could not be started message', async () => {
      const mockerImportState = mockSystemImportStateQuery({
        systemImportState: {
          result: null,
          finishedAt: null,
          startedAt: null,
        },
      })

      const view = await visitView('/guided-setup/import/freshdesk/status')

      const mockCalls = await mockerImportState.waitForCalls()
      expect(mockCalls).toHaveLength(1)

      await vi.advanceTimersByTimeAsync(90000)

      await waitForNextTick()

      const errorMessage = await view.findByText(
        'Background process did not start or has not finished! Please contact your support.',
      )
      expect(errorMessage.role).toBe('alert')
      expect(errorMessage).toBeInTheDocument()
    })

    it('shows server error after some status updates and hide it again', async () => {
      const startDate = new Date().toISOString()
      const mockerImportState = mockSystemImportStateQuery({
        systemImportState: {
          result: {
            Groups: { sum: 5, total: 10 },
          },
          finishedAt: null,
          startedAt: startDate,
        },
      })

      const view = await visitView('/guided-setup/import/freshdesk/status')

      const mockCalls = await mockerImportState.waitForCalls()
      expect(mockCalls).toHaveLength(1)

      mockSystemImportStateQuery({
        systemImportState: {
          result: {
            Groups: { sum: 8, total: 10 },
            error: 'An error occurred while importing data.',
          },
          finishedAt: null,
          startedAt: startDate,
        },
      })

      await vi.advanceTimersByTimeAsync(5000)
      expect(mockCalls).toHaveLength(2)

      await waitForNextTick()

      const errorMessage = await view.findByText(
        'An error occurred while importing data.',
      )
      expect(errorMessage.role).toBe('alert')
      expect(errorMessage).toBeInTheDocument()

      mockSystemImportStateQuery({
        systemImportState: {
          result: {
            Groups: { sum: 10, total: 10 },
            Organizations: { sum: 50, total: 100 },
          },
          finishedAt: null,
          startedAt: startDate,
        },
      })

      await vi.advanceTimersByTimeAsync(5000)
      expect(mockCalls).toHaveLength(3)

      await waitForNextTick()

      expect(errorMessage).not.toBeInTheDocument()
    })

    it('shows server error during the start (should hide start loading) and stop polling after some time', async () => {
      const mockerImportState = mockSystemImportStateQuery({
        systemImportState: {
          result: null,
          finishedAt: null,
          startedAt: null,
        },
      })

      const view = await visitView('/guided-setup/import/freshdesk/status')

      const mockCalls = await mockerImportState.waitForCalls()
      expect(mockCalls).toHaveLength(1)

      expect(view.getByText('Starting import…')).toBeInTheDocument()

      mockSystemImportStateQuery({
        systemImportState: {
          result: {
            error: 'An error occurred while importing data.',
          },
          finishedAt: null,
          startedAt: null,
        },
      })

      await vi.advanceTimersByTimeAsync(5000)
      expect(mockCalls).toHaveLength(2)

      await waitForNextTick()

      const errorMessage = await view.findByText(
        'An error occurred while importing data.',
      )
      expect(errorMessage.role).toBe('alert')
      expect(errorMessage).toBeInTheDocument()
      expect(view.queryByText('Starting import…')).not.toBeInTheDocument()

      // Stop polling after 90 seconds, so it should not be more then 19 calls.
      await vi.advanceTimersByTimeAsync(100000)
      expect(mockCalls).toHaveLength(19)
    })
  })
})