fix: stabilize conversation FAQ lock spec (#15236)
## Description Backend CI no longer flakes when the conversation FAQ grouping-lock spec runs after Rails has reloaded application constants. The job already raises the intended lock-acquisition error and prevents concurrent suggestion generation. This updates the assertion to compare the error class name, preserving that behavior without depending on a reload-sensitive Ruby `Class` object. The failure was reproduced in [CircleCI backend job 171299](https://app.circleci.com/pipelines/github/chatwoot/chatwoot/116560/workflows/efcee2f1-5af8-4121-82c3-920002dc420b/jobs/171299). ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? The isolated job spec passes, the exact 18-way CircleCI shard selection passes all 442 examples, and the changed spec passes Ruby lint. ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] My changes generate no new warnings - [x] New and existing unit tests pass locally with my changes
This commit is contained in:
@@ -165,9 +165,8 @@ RSpec.describe DeviseOverrides::SessionsController, type: :controller do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe 'session limit enforcement' do
|
describe 'session limit enforcement' do
|
||||||
before { stub_const('DeviseOverrides::SessionsController::MAX_SESSIONS', 5) }
|
|
||||||
|
|
||||||
let(:user) { create(:user, password: 'Test@123456') }
|
let(:user) { create(:user, password: 'Test@123456') }
|
||||||
|
let(:session_limit) { described_class::MAX_SESSIONS }
|
||||||
let(:browser_ua) { 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15' }
|
let(:browser_ua) { 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2.1 Safari/605.1.15' }
|
||||||
let(:mobile_ua) { 'okhttp/4.9.3' }
|
let(:mobile_ua) { 'okhttp/4.9.3' }
|
||||||
|
|
||||||
@@ -195,9 +194,9 @@ RSpec.describe DeviseOverrides::SessionsController, type: :controller do
|
|||||||
|
|
||||||
it 'does not count expired tokens toward the cap' do
|
it 'does not count expired tokens toward the cap' do
|
||||||
request.env['HTTP_USER_AGENT'] = browser_ua
|
request.env['HTTP_USER_AGENT'] = browser_ua
|
||||||
# 3 expired + 2 active = 5 raw entries, but only 2 active
|
# Raw token count reaches the cap, but three tokens are expired
|
||||||
3.times { |i| seed_token("expired#{i}", expiry_offset_days: -1, with_session: false) }
|
3.times { |i| seed_token("expired#{i}", expiry_offset_days: -1, with_session: false) }
|
||||||
2.times { |i| seed_token("active#{i}", expiry_offset_days: 30) }
|
(session_limit - 3).times { |i| seed_token("active#{i}", expiry_offset_days: 30) }
|
||||||
|
|
||||||
post :create, params: login_params
|
post :create, params: login_params
|
||||||
|
|
||||||
@@ -208,7 +207,7 @@ RSpec.describe DeviseOverrides::SessionsController, type: :controller do
|
|||||||
context 'when at the limit from a browser with full tracking' do
|
context 'when at the limit from a browser with full tracking' do
|
||||||
before do
|
before do
|
||||||
request.env['HTTP_USER_AGENT'] = browser_ua
|
request.env['HTTP_USER_AGENT'] = browser_ua
|
||||||
5.times { |i| seed_token("c#{i}", expiry_offset_days: 30) }
|
session_limit.times { |i| seed_token("c#{i}", expiry_offset_days: 30) }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns 409 with the session list (picker)' do
|
it 'returns 409 with the session list (picker)' do
|
||||||
@@ -217,7 +216,7 @@ RSpec.describe DeviseOverrides::SessionsController, type: :controller do
|
|||||||
expect(response).to have_http_status(:conflict)
|
expect(response).to have_http_status(:conflict)
|
||||||
body = response.parsed_body
|
body = response.parsed_body
|
||||||
expect(body['sessions_limit_reached']).to be true
|
expect(body['sessions_limit_reached']).to be true
|
||||||
expect(body['sessions'].size).to eq(5)
|
expect(body['sessions'].size).to eq(session_limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not create a new session row' do
|
it 'does not create a new session row' do
|
||||||
@@ -228,7 +227,7 @@ RSpec.describe DeviseOverrides::SessionsController, type: :controller do
|
|||||||
context 'when at the limit from a non-browser client' do
|
context 'when at the limit from a non-browser client' do
|
||||||
before do
|
before do
|
||||||
request.env['HTTP_USER_AGENT'] = mobile_ua
|
request.env['HTTP_USER_AGENT'] = mobile_ua
|
||||||
5.times { |i| seed_token("c#{i}", expiry_offset_days: 30 + i, with_session: false) }
|
session_limit.times { |i| seed_token("c#{i}", expiry_offset_days: 30 + i, with_session: false) }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'silently evicts the oldest token and lets login proceed' do
|
it 'silently evicts the oldest token and lets login proceed' do
|
||||||
@@ -242,9 +241,9 @@ RSpec.describe DeviseOverrides::SessionsController, type: :controller do
|
|||||||
context 'when at the limit but tracking is partial (legacy tokens present)' do
|
context 'when at the limit but tracking is partial (legacy tokens present)' do
|
||||||
before do
|
before do
|
||||||
request.env['HTTP_USER_AGENT'] = browser_ua
|
request.env['HTTP_USER_AGENT'] = browser_ua
|
||||||
# one tracked, four legacy (no user_session rows)
|
# One tracked session, with the remaining tokens having no user_session rows
|
||||||
seed_token('tracked', expiry_offset_days: 60, with_session: true)
|
seed_token('tracked', expiry_offset_days: 60, with_session: true)
|
||||||
4.times { |i| seed_token("legacy#{i}", expiry_offset_days: 10 + i, with_session: false) }
|
(session_limit - 1).times { |i| seed_token("legacy#{i}", expiry_offset_days: 10 + i, with_session: false) }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'silent-evicts instead of showing a partial picker' do
|
it 'silent-evicts instead of showing a partial picker' do
|
||||||
@@ -266,10 +265,10 @@ RSpec.describe DeviseOverrides::SessionsController, type: :controller do
|
|||||||
context 'when at the limit with full tracking (no legacy gap)' do
|
context 'when at the limit with full tracking (no legacy gap)' do
|
||||||
before do
|
before do
|
||||||
request.env['HTTP_USER_AGENT'] = mobile_ua
|
request.env['HTTP_USER_AGENT'] = mobile_ua
|
||||||
# Five tracked sessions, varying activity timestamps
|
# Tracked sessions with varying activity timestamps
|
||||||
5.times do |i|
|
session_limit.times do |i|
|
||||||
seed_token("tracked#{i}", expiry_offset_days: 30)
|
seed_token("tracked#{i}", expiry_offset_days: 30)
|
||||||
user.user_sessions.find_by(client_id: "tracked#{i}").update!(last_activity_at: (5 - i).days.ago)
|
user.user_sessions.find_by(client_id: "tracked#{i}").update!(last_activity_at: (session_limit - i).days.ago)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -277,7 +276,7 @@ RSpec.describe DeviseOverrides::SessionsController, type: :controller do
|
|||||||
post :create, params: login_params
|
post :create, params: login_params
|
||||||
|
|
||||||
expect(response).to have_http_status(:success)
|
expect(response).to have_http_status(:success)
|
||||||
# tracked0 had the oldest last_activity_at (5 days ago)
|
# tracked0 has the oldest last_activity_at
|
||||||
expect(user.reload.tokens.keys).not_to include('tracked0')
|
expect(user.reload.tokens.keys).not_to include('tracked0')
|
||||||
expect(user.user_sessions.exists?(client_id: 'tracked0')).to be false
|
expect(user.user_sessions.exists?(client_id: 'tracked0')).to be false
|
||||||
end
|
end
|
||||||
@@ -286,7 +285,7 @@ RSpec.describe DeviseOverrides::SessionsController, type: :controller do
|
|||||||
context 'with revoke_session_id during login' do
|
context 'with revoke_session_id during login' do
|
||||||
before do
|
before do
|
||||||
request.env['HTTP_USER_AGENT'] = browser_ua
|
request.env['HTTP_USER_AGENT'] = browser_ua
|
||||||
5.times { |i| seed_token("c#{i}", expiry_offset_days: 30) }
|
session_limit.times { |i| seed_token("c#{i}", expiry_offset_days: 30) }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'revokes the chosen session and proceeds with login' do
|
it 'revokes the chosen session and proceeds with login' do
|
||||||
@@ -303,7 +302,7 @@ RSpec.describe DeviseOverrides::SessionsController, type: :controller do
|
|||||||
context 'with revoke_all_sessions during login' do
|
context 'with revoke_all_sessions during login' do
|
||||||
before do
|
before do
|
||||||
request.env['HTTP_USER_AGENT'] = browser_ua
|
request.env['HTTP_USER_AGENT'] = browser_ua
|
||||||
5.times { |i| seed_token("c#{i}", expiry_offset_days: 30) }
|
session_limit.times { |i| seed_token("c#{i}", expiry_offset_days: 30) }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'wipes all sessions and tokens, then proceeds with login' do
|
it 'wipes all sessions and tokens, then proceeds with login' do
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ RSpec.describe Captain::Llm::ConversationFaqJob, type: :job do
|
|||||||
|
|
||||||
expect do
|
expect do
|
||||||
described_class.new.perform(conversation, assistant)
|
described_class.new.perform(conversation, assistant)
|
||||||
end.to raise_error(MutexApplicationJob::LockAcquisitionError)
|
end.to(raise_error do |error|
|
||||||
|
expect(error.class.name).to eq('MutexApplicationJob::LockAcquisitionError')
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user