Skip to content

Commit 8ef06e2

Browse files
committed
Add test coverage for internal v2 token generation
Add request specs for InternalUserAccessTokensController - Include both authenticated & unauthenticated user scenarios - Include both present & absent internal OAuth app scenarios Add service specs for InternalUserAccessTokenService - Test token retrieval, rotation, and OAuth app presence - Verify old token revocation when rotating Add view specs for API token partials - Test legacy partial rendering based on `user.can_use_api?` - Test OAuth application availability scenarios
1 parent 5436190 commit 8ef06e2

4 files changed

Lines changed: 269 additions & 0 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe Api::V2::InternalUserAccessTokensController do
6+
let(:user) { create(:user) }
7+
let(:app_name) { Rails.application.config.x.application.internal_oauth_app_name }
8+
let!(:oauth_app) { create(:oauth_application, name: app_name) }
9+
10+
describe 'POST #create' do
11+
def post_create_token
12+
post api_v2_internal_user_access_token_path(format: :js)
13+
end
14+
15+
context 'when user is not authenticated' do
16+
# In production, CSRF protection would reject the request with a 422 error
17+
# before it reaches Pundit. However, RSpec bypasses CSRF checks, so this
18+
# test verifies that Pundit raises NotDefinedError when authorize is called
19+
# with nil. This error won't occur in production due to CSRF protection.
20+
it 'raises Pundit::NotDefinedError and does not create a token' do
21+
expect do
22+
expect do
23+
post_create_token
24+
end.to raise_error(Pundit::NotDefinedError)
25+
end.not_to change { Doorkeeper::AccessToken.count }
26+
end
27+
end
28+
29+
context 'when user is authenticated' do
30+
before { sign_in(user) }
31+
32+
it 'rotates the user token' do
33+
post_create_token
34+
35+
expect(response).to have_http_status(:ok)
36+
end
37+
38+
it 'creates a new token' do
39+
expect do
40+
post_create_token
41+
end.to change { Doorkeeper::AccessToken.count }.by(1)
42+
end
43+
44+
it 'assigns the token' do
45+
post_create_token
46+
47+
expect(assigns(:token)).to be_a(Doorkeeper::AccessToken)
48+
expect(assigns(:token).resource_owner_id).to eq(user.id)
49+
end
50+
51+
it 'renders the refresh_token template' do
52+
post_create_token
53+
54+
expect(response).to render_template('users/refresh_token')
55+
end
56+
57+
context 'when a token already exists' do
58+
let!(:old_token) do
59+
create(:oauth_access_token, application: oauth_app, resource_owner_id: user.id, scopes: 'read')
60+
end
61+
62+
it 'revokes the old token' do
63+
post_create_token
64+
65+
old_token.reload
66+
expect(old_token.revoked_at).not_to be_nil
67+
end
68+
69+
it 'creates a new token' do
70+
post_create_token
71+
72+
new_token = assigns(:token)
73+
expect(new_token).not_to eq(old_token)
74+
end
75+
end
76+
end
77+
78+
context 'when the internal OAuth application is missing' do
79+
before do
80+
sign_in(user)
81+
oauth_app.destroy
82+
end
83+
84+
it 'raises a StandardError' do
85+
expect do
86+
post_create_token
87+
end.to raise_error(StandardError, /not found/)
88+
end
89+
end
90+
end
91+
end
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe Api::V2::InternalUserAccessTokenService do
6+
let(:user) { create(:user) }
7+
let(:app_name) { Rails.application.config.x.application.internal_oauth_app_name }
8+
let!(:oauth_app) { create(:oauth_application, name: app_name) }
9+
10+
def create_internal_user_access_token
11+
create(:oauth_access_token, application: oauth_app, resource_owner_id: user.id, scopes: 'read')
12+
end
13+
14+
describe '#for_user' do
15+
context 'when a token exists for the user' do
16+
let!(:access_token) do
17+
create_internal_user_access_token
18+
end
19+
20+
it 'returns the access token' do
21+
token = described_class.for_user(user)
22+
expect(token).to be_present
23+
expect(token.resource_owner_id).to eq(user.id)
24+
end
25+
end
26+
27+
context 'when no token exists for the user' do
28+
it 'returns nil' do
29+
token = described_class.for_user(user)
30+
expect(token).to be_nil
31+
end
32+
end
33+
end
34+
35+
describe '#rotate!' do
36+
def rotate_token_expectations(new_token, old_token = nil) # rubocop:disable Metrics/AbcSize
37+
expect(new_token).to be_persisted
38+
expect(new_token.resource_owner_id).to eq(user.id)
39+
expect(new_token.revoked_at).to be_nil
40+
expect(new_token.scopes.to_s).to include('read')
41+
return unless old_token
42+
43+
expect(new_token).not_to eq(old_token)
44+
expect(old_token.revoked_at).not_to be_nil
45+
end
46+
47+
context 'when a token already exists' do
48+
let!(:old_token) do
49+
create_internal_user_access_token
50+
end
51+
52+
it 'revokes the old token and creates a new one' do
53+
new_token = described_class.rotate!(user)
54+
old_token.reload
55+
rotate_token_expectations(new_token, old_token)
56+
end
57+
end
58+
59+
context 'when no token exists' do
60+
it 'creates a new token' do
61+
token = described_class.rotate!(user)
62+
rotate_token_expectations(token)
63+
end
64+
end
65+
end
66+
67+
describe '#application_present?' do
68+
context 'when the app exists' do
69+
it 'returns true' do
70+
expect(described_class.application_present?).to be true
71+
end
72+
end
73+
74+
context 'when the app does not exist' do
75+
before { oauth_app.destroy }
76+
77+
it 'returns false' do
78+
expect(described_class.application_present?).to be false
79+
end
80+
end
81+
end
82+
end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe 'devise/registrations/_api_token.html.erb' do
6+
let(:app_name) { Rails.application.config.x.application.internal_oauth_app_name }
7+
let!(:oauth_app) { create(:oauth_application, name: app_name) }
8+
9+
before do
10+
# Clear memoization between tests
11+
Api::V2::InternalUserAccessTokenService.instance_variable_set(:@application, nil)
12+
end
13+
14+
context 'When a user has the `use_api` permission' do
15+
it 'renders both the v2 and legacy API token sections' do
16+
user = create(:user, :org_admin)
17+
18+
render partial: 'devise/registrations/api_token', locals: { user: user }
19+
20+
expect(rendered).to have_selector('#v2-api-token')
21+
expect(rendered).to have_selector('#legacy-api-token')
22+
end
23+
end
24+
25+
context 'When a user does not have the `use_api` permission' do
26+
it 'renders only the v2 API token section' do
27+
user = create(:user)
28+
29+
render partial: 'devise/registrations/api_token', locals: { user: user }
30+
31+
expect(rendered).to have_selector('#v2-api-token')
32+
expect(rendered).not_to have_selector('#legacy-api-token')
33+
end
34+
end
35+
end
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe 'devise/registrations/_v2_api_token.html.erb' do
6+
let(:user) { create(:user) }
7+
let(:app_name) { Rails.application.config.x.application.internal_oauth_app_name }
8+
9+
context 'when the OAuth application exists' do
10+
let!(:oauth_app) { create(:oauth_application, name: app_name) }
11+
12+
it 'displays the regenerate button' do
13+
render partial: 'devise/registrations/v2_api_token', locals: { user: user }
14+
15+
expect(rendered).to have_link('Regenerate token',
16+
href: api_v2_internal_user_access_token_path(format: :js))
17+
end
18+
19+
context 'when user has a token' do
20+
let!(:token) do
21+
create(:oauth_access_token,
22+
application: oauth_app,
23+
resource_owner_id: user.id,
24+
scopes: 'read')
25+
end
26+
27+
it 'displays the token' do
28+
render partial: 'devise/registrations/v2_api_token', locals: { user: user }
29+
30+
expect(rendered).to have_selector('code', text: token.token)
31+
expect(rendered).not_to have_content('Click the button below to generate an API token')
32+
end
33+
end
34+
35+
context 'when user does not have a token' do
36+
it 'displays the generate message' do
37+
render partial: 'devise/registrations/v2_api_token', locals: { user: user }
38+
39+
expect(rendered).to have_content('Click the button below to generate an API token')
40+
expect(rendered).not_to have_selector('code')
41+
end
42+
end
43+
end
44+
45+
context 'when the OAuth application does not exist' do
46+
it 'displays the warning message and helpdesk email link' do
47+
render partial: 'devise/registrations/v2_api_token', locals: { user: user }
48+
49+
expect(rendered).to have_selector('.alert-warning')
50+
expect(rendered).to have_content('V2 API token service is currently unavailable')
51+
expect(rendered).to have_link(href: "mailto:#{Rails.application.config.x.organisation.helpdesk_email}")
52+
end
53+
54+
it 'does not display the token or regenerate button' do
55+
render partial: 'devise/registrations/v2_api_token', locals: { user: user }
56+
57+
expect(rendered).not_to have_link('Regenerate token')
58+
expect(rendered).not_to have_selector('code')
59+
end
60+
end
61+
end

0 commit comments

Comments
 (0)