This PR adds a reload-safe onboarding Help Center generation status path. Previously, generation progress was pushed through ActionCable, which meant the onboarding UI could lose context after a page reload or missed websocket event. The new endpoint exposes the current generation id, raw Redis generation state, and Help Center article/category counts so clients can recover state by fetching from the backend. **What changed** - Added an onboarding Help Center generation status endpoint. - Persisted `help_center_generation_id` when generation is enqueued. - Removed Help Center generation ActionCable broadcasts completely. - Kept generation progress in Redis as the backend source of truth. - Kept Help Center generation out of the onboarding hot path; the existing onboarding controller does not start generation yet. **How to test** 1. Start Help Center generation for an account. 2. Fetch the onboarding generation status endpoint and verify it returns the generation id, Redis state, and article/category counts. 3. Reload the onboarding UI or client state and fetch again to confirm progress can be recovered without relying on websocket events. 4. Verify skipped/completed generation states are reflected from Redis. ## Related PRs - https://github.com/chatwoot/chatwoot/pull/14569 - https://github.com/chatwoot/chatwoot/pull/14568 - https://github.com/chatwoot/chatwoot/pull/14567 - https://github.com/chatwoot/chatwoot/pull/14619 - https://github.com/chatwoot/chatwoot/pull/14565 (Primary onboarding PR)
151 lines
5.7 KiB
Ruby
151 lines
5.7 KiB
Ruby
require 'rails_helper'
|
|
|
|
RSpec.describe 'Onboarding API', type: :request do
|
|
let(:account) { create(:account, domain: 'example.com') }
|
|
let(:admin) { create(:user, account: account, role: :administrator) }
|
|
|
|
describe 'PATCH /api/v1/accounts/{account.id}/onboarding' do
|
|
context 'when unauthenticated' do
|
|
it 'returns unauthorized' do
|
|
patch "/api/v1/accounts/#{account.id}/onboarding", params: { website: 'acme.com' }, as: :json
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when authenticated as an agent (non-admin)' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
|
|
it 'returns unauthorized and does not change the account' do
|
|
patch "/api/v1/accounts/#{account.id}/onboarding",
|
|
params: { name: 'Hijacked', website: 'attacker.com' },
|
|
headers: agent.create_new_auth_token, as: :json
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
expect(account.reload.name).not_to eq('Hijacked')
|
|
end
|
|
|
|
it 'does not create a help center portal' do
|
|
account.update!(custom_attributes: { 'onboarding_step' => 'account_details' })
|
|
|
|
expect do
|
|
patch "/api/v1/accounts/#{account.id}/onboarding",
|
|
params: { website: 'attacker.com' },
|
|
headers: agent.create_new_auth_token, as: :json
|
|
end.not_to change(account.portals, :count)
|
|
end
|
|
end
|
|
|
|
context 'when finalizing account_details' do
|
|
before { account.update!(custom_attributes: { 'onboarding_step' => 'account_details' }) }
|
|
|
|
it 'saves name and locale' do
|
|
patch "/api/v1/accounts/#{account.id}/onboarding",
|
|
params: { name: 'Acme Inc', locale: 'fr' },
|
|
headers: admin.create_new_auth_token, as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(account.reload.name).to eq('Acme Inc')
|
|
expect(account.locale).to eq('fr')
|
|
end
|
|
|
|
it 'merges custom_attributes' do
|
|
patch "/api/v1/accounts/#{account.id}/onboarding",
|
|
params: { website: 'acme.com', industry: 'tech', company_size: '10-50' },
|
|
headers: admin.create_new_auth_token, as: :json
|
|
|
|
attrs = account.reload.custom_attributes
|
|
expect(attrs['website']).to eq('acme.com')
|
|
expect(attrs['industry']).to eq('tech')
|
|
expect(attrs['company_size']).to eq('10-50')
|
|
end
|
|
|
|
it 'clears onboarding_step' do
|
|
patch "/api/v1/accounts/#{account.id}/onboarding",
|
|
params: { website: 'acme.com' },
|
|
headers: admin.create_new_auth_token, as: :json
|
|
|
|
expect(account.reload.custom_attributes).not_to have_key('onboarding_step')
|
|
end
|
|
|
|
it 'invokes HelpCenterCreationService when website is present', skip: 'help center generation wiring disabled until UI is ready' do
|
|
service = instance_double(Onboarding::HelpCenterCreationService, perform: nil)
|
|
allow(Onboarding::HelpCenterCreationService).to receive(:new).and_return(service)
|
|
|
|
patch "/api/v1/accounts/#{account.id}/onboarding",
|
|
params: { website: 'acme.com' },
|
|
headers: admin.create_new_auth_token, as: :json
|
|
|
|
expect(Onboarding::HelpCenterCreationService).to have_received(:new) do |arg_account, arg_user|
|
|
expect(arg_account.id).to eq(account.id)
|
|
expect(arg_user.id).to eq(admin.id)
|
|
end
|
|
expect(service).to have_received(:perform)
|
|
end
|
|
|
|
it 'does not create a help center portal when website is blank' do
|
|
expect do
|
|
patch "/api/v1/accounts/#{account.id}/onboarding",
|
|
params: { name: 'Acme Inc' },
|
|
headers: admin.create_new_auth_token, as: :json
|
|
end.not_to change(account.portals, :count)
|
|
end
|
|
end
|
|
|
|
context 'when onboarding_step is not account_details' do
|
|
before { account.update!(custom_attributes: { 'onboarding_step' => 'invite_team' }) }
|
|
|
|
it 'does not clear onboarding_step' do
|
|
patch "/api/v1/accounts/#{account.id}/onboarding",
|
|
params: { website: 'acme.com' },
|
|
headers: admin.create_new_auth_token, as: :json
|
|
|
|
expect(account.reload.custom_attributes['onboarding_step']).to eq('invite_team')
|
|
end
|
|
|
|
it 'does not create a help center portal' do
|
|
expect do
|
|
patch "/api/v1/accounts/#{account.id}/onboarding",
|
|
params: { website: 'acme.com' },
|
|
headers: admin.create_new_auth_token, as: :json
|
|
end.not_to change(account.portals, :count)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe 'GET /api/v1/accounts/{account.id}/onboarding/help_center_generation' do
|
|
context 'when unauthenticated' do
|
|
it 'returns unauthorized' do
|
|
get "/api/v1/accounts/#{account.id}/onboarding/help_center_generation", as: :json
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when authenticated as an agent (non-admin)' do
|
|
let(:agent) { create(:user, account: account, role: :agent) }
|
|
|
|
it 'returns unauthorized' do
|
|
get "/api/v1/accounts/#{account.id}/onboarding/help_center_generation",
|
|
headers: agent.create_new_auth_token, as: :json
|
|
|
|
expect(response).to have_http_status(:unauthorized)
|
|
end
|
|
end
|
|
|
|
context 'when no help center generation has started' do
|
|
it 'returns not_started with zero counts' do
|
|
get "/api/v1/accounts/#{account.id}/onboarding/help_center_generation",
|
|
headers: admin.create_new_auth_token, as: :json
|
|
|
|
expect(response).to have_http_status(:success)
|
|
expect(response.parsed_body).to include(
|
|
'generation_id' => nil,
|
|
'state' => nil,
|
|
'articles_count' => 0,
|
|
'categories_count' => 0
|
|
)
|
|
end
|
|
end
|
|
end
|
|
end
|