mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 19:17:48 +00:00 
			
		
		
		
	 b28c08059f
			
		
	
	b28c08059f
	
	
	
		
			
			# Pull Request Template ## Description This PR fixes the incorrect contact access in conversations listing API. Cause: - `undefined method 'conversations' for nil` error because `@contact` is not initialized Solution: - Using `@contact_inbox` to access `@contact` - `@contact_inbox` is properly set in the parent controller's `set_contact_inbox` method Fixes https://linear.app/chatwoot/issue/CW-4185/incorrect-contact-access-pattern-in ## Type of change Please delete options that are not relevant. - [x] Bug fix (non-breaking change which fixes an issue) ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
		
			
				
	
	
		
			144 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			144 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
| require 'rails_helper'
 | |
| 
 | |
| RSpec.describe 'Public Inbox Contact Conversations API', type: :request do
 | |
|   let!(:api_channel) { create(:channel_api) }
 | |
|   let!(:contact) { create(:contact) }
 | |
|   let!(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: api_channel.inbox) }
 | |
| 
 | |
|   describe 'GET /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations' do
 | |
|     it 'return the conversations for that contact' do
 | |
|       create(:conversation, contact_inbox: contact_inbox)
 | |
|       get "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations"
 | |
| 
 | |
|       expect(response).to have_http_status(:success)
 | |
|       data = response.parsed_body
 | |
|       expect(data.length).to eq 1
 | |
|       expect(data.first['uuid']).to eq contact_inbox.conversations.first.uuid
 | |
|     end
 | |
| 
 | |
|     it 'return the conversations when hmac_verified is true' do
 | |
|       contact_inbox.update(hmac_verified: true)
 | |
|       create(:conversation, contact: contact)
 | |
|       get "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations"
 | |
| 
 | |
|       expect(response).to have_http_status(:success)
 | |
|       data = response.parsed_body
 | |
|       expect(data.length).to eq 1
 | |
|       expect(data.first['uuid']).to eq contact.conversations.first.uuid
 | |
|     end
 | |
| 
 | |
|     it 'does not return any private or activity message' do
 | |
|       conversation = create(:conversation, contact_inbox: contact_inbox)
 | |
|       create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation, content: 'message-1')
 | |
|       create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation, content: 'message-2')
 | |
|       create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation, content: 'private-message-1',
 | |
|                        private: true)
 | |
|       create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation, content: 'activity-message-1',
 | |
|                        message_type: :activity)
 | |
| 
 | |
|       get "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations"
 | |
| 
 | |
|       expect(response).to have_http_status(:success)
 | |
|       data = response.parsed_body
 | |
|       expect(data.length).to eq 1
 | |
|       expect(data.first['messages'].length).to eq 2
 | |
|       expect(data.first['messages'].pluck('content')).not_to include('private-message-1')
 | |
|       expect(data.first['messages'].pluck('message_type')).not_to include('activity')
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe 'GET /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations/{conversation_id}' do
 | |
|     it 'returns the conversation that the contact has access to' do
 | |
|       conversation = create(:conversation, contact_inbox: contact_inbox)
 | |
|       create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation, content: 'message-1')
 | |
|       create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation, content: 'message-2')
 | |
| 
 | |
|       get "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{conversation.display_id}"
 | |
| 
 | |
|       expect(response).to have_http_status(:success)
 | |
|       data = response.parsed_body
 | |
|       expect(data['id']).to eq(conversation.display_id)
 | |
|       expect(data['messages']).to be_a(Array)
 | |
|       expect(data['messages'].length).to eq(conversation.messages.count)
 | |
|       expect(data['messages'].pluck('content')).to include(conversation.messages.first.content)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe 'POST /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations/{conversation_id}/toggle_status' do
 | |
|     it 'resolves the conversation' do
 | |
|       conversation = create(:conversation, contact_inbox: contact_inbox)
 | |
|       display_id = conversation.display_id
 | |
| 
 | |
|       post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{display_id}/toggle_status"
 | |
| 
 | |
|       expect(response).to have_http_status(:success)
 | |
|       expect(conversation.reload).to be_resolved
 | |
|     end
 | |
| 
 | |
|     it 'does not resolve a conversation that is already resolved' do
 | |
|       conversation = create(:conversation, contact_inbox: contact_inbox, status: :resolved)
 | |
|       display_id = conversation.display_id
 | |
| 
 | |
|       post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{display_id}/toggle_status"
 | |
| 
 | |
|       expect(response).to have_http_status(:success)
 | |
|       expect(Conversation.where(id: conversation.id, status: :resolved).count).to eq(1)
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe 'POST /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations' do
 | |
|     it 'creates a conversation for that contact' do
 | |
|       post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations"
 | |
| 
 | |
|       expect(response).to have_http_status(:success)
 | |
|       data = response.parsed_body
 | |
|       expect(data['id']).not_to be_nil
 | |
|     end
 | |
| 
 | |
|     it 'creates a conversation with custom attributes but prevents other attributes' do
 | |
|       post "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations",
 | |
|            params: { custom_attributes: { 'test' => 'test' }, additional_attributes: { 'test' => 'test' } }
 | |
| 
 | |
|       expect(response).to have_http_status(:success)
 | |
|       data = response.parsed_body
 | |
|       conversation = api_channel.inbox.conversations.find_by(display_id: data['id'])
 | |
|       expect(conversation.custom_attributes).to eq('test' => 'test')
 | |
|       expect(conversation.additional_attributes).to be_empty
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe 'POST /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations/{conversation_id}/toggle_typing' do
 | |
|     let!(:conversation) { create(:conversation, contact_inbox: contact_inbox, contact: contact) }
 | |
|     let(:toggle_typing_path) do
 | |
|       "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{conversation.display_id}/toggle_typing"
 | |
|     end
 | |
| 
 | |
|     it 'dispatches the correct typing status' do
 | |
|       allow(Rails.configuration.dispatcher).to receive(:dispatch)
 | |
|       post toggle_typing_path, params: { typing_status: 'on' }
 | |
| 
 | |
|       expect(response).to have_http_status(:success)
 | |
|       expect(Rails.configuration.dispatcher).to have_received(:dispatch)
 | |
|         .with(Conversation::CONVERSATION_TYPING_ON, kind_of(Time), { conversation: conversation, user: contact })
 | |
|     end
 | |
|   end
 | |
| 
 | |
|   describe 'POST /public/api/v1/inboxes/{identifier}/contact/{source_id}/conversations/{conversation_id}/update_last_seen' do
 | |
|     let!(:conversation) { create(:conversation, contact_inbox: contact_inbox, contact: contact) }
 | |
|     let(:update_last_seen_path) do
 | |
|       "/public/api/v1/inboxes/#{api_channel.identifier}/contacts/#{contact_inbox.source_id}/conversations/#{conversation.display_id}/update_last_seen"
 | |
|     end
 | |
| 
 | |
|     it 'updates the last seen of the conversation contact' do
 | |
|       current_time = DateTime.now.utc
 | |
|       allow(DateTime).to receive(:now).and_return(current_time)
 | |
|       contact_last_seen_at = conversation.contact_last_seen_at
 | |
|       expect(Conversations::UpdateMessageStatusJob).to receive(:perform_later).with(conversation.id, current_time)
 | |
|       post update_last_seen_path
 | |
| 
 | |
|       expect(response).to have_http_status(:success)
 | |
|       expect(conversation.reload.contact_last_seen_at).not_to eq contact_last_seen_at
 | |
|     end
 | |
|   end
 | |
| end
 |