rolemodel-rails 1.1.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +117 -7
  3. data/lib/generators/rolemodel/all_generator.rb +0 -1
  4. data/lib/generators/rolemodel/optics/icons/README.md +6 -1
  5. data/lib/generators/rolemodel/optics/icons/icons_generator.rb +14 -9
  6. data/lib/generators/rolemodel/optics/icons/templates/app/helpers/icon_helper.rb.tt +4 -2
  7. data/lib/generators/rolemodel/testing/README.md +4 -1
  8. data/lib/generators/rolemodel/testing/all_generator.rb +3 -3
  9. data/lib/generators/rolemodel/testing/jasmine_playwright/jasmine_playwright_generator.rb +1 -1
  10. data/lib/generators/rolemodel/testing/rspec/README.md +1 -1
  11. data/lib/generators/rolemodel/testing/rspec/USAGE +1 -1
  12. data/lib/generators/rolemodel/testing/rspec/rspec_generator.rb +15 -14
  13. data/lib/generators/rolemodel/testing/rspec/templates/spec/support/capybara_config.rb.tt +9 -0
  14. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/custom_icon_builder.rb +1 -1
  15. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/feather_icon_builder.rb +1 -1
  16. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/icon_builder.rb +10 -19
  17. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/lucide_icon_builder.rb +1 -1
  18. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/material_icon_builder.rb +1 -1
  19. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/phosphor_icon_builder.rb +1 -1
  20. data/lib/{generators/rolemodel/optics/icons/templates/app/icon_builders → rolemodel/optics}/tabler_icon_builder.rb +1 -1
  21. data/lib/rolemodel/optics.rb +9 -0
  22. data/lib/rolemodel/utility/task_tools.rb +69 -0
  23. data/lib/rolemodel/utility.rb +3 -0
  24. data/lib/rolemodel/version.rb +1 -1
  25. data/lib/rolemodel-rails.rb +2 -0
  26. metadata +35 -42
  27. data/lib/generators/rolemodel/mcp/README.md +0 -13
  28. data/lib/generators/rolemodel/mcp/USAGE +0 -8
  29. data/lib/generators/rolemodel/mcp/mcp_generator.rb +0 -110
  30. data/lib/generators/rolemodel/mcp/templates/app/assets/stylesheets/components/doorkeeper.css +0 -140
  31. data/lib/generators/rolemodel/mcp/templates/app/controllers/doorkeeper/base_controller.rb +0 -7
  32. data/lib/generators/rolemodel/mcp/templates/app/controllers/mcp_controller.rb.tt +0 -91
  33. data/lib/generators/rolemodel/mcp/templates/app/controllers/oauth_registrations_controller.rb +0 -46
  34. data/lib/generators/rolemodel/mcp/templates/app/controllers/well_known_controller.rb +0 -39
  35. data/lib/generators/rolemodel/mcp/templates/app/mcp/prompts/sample.rb +0 -36
  36. data/lib/generators/rolemodel/mcp/templates/app/mcp/resources/controller.rb +0 -57
  37. data/lib/generators/rolemodel/mcp/templates/app/mcp/resources/docs/SAMPLE_DOC.md +0 -4
  38. data/lib/generators/rolemodel/mcp/templates/app/mcp/resources/docs_controller.rb +0 -46
  39. data/lib/generators/rolemodel/mcp/templates/app/mcp/tools/sample.rb +0 -42
  40. data/lib/generators/rolemodel/mcp/templates/app/views/doorkeeper/authorizations/error.html.slim.tt +0 -13
  41. data/lib/generators/rolemodel/mcp/templates/app/views/doorkeeper/authorizations/new.html.slim.tt +0 -41
  42. data/lib/generators/rolemodel/mcp/templates/app/views/layouts/doorkeeper.html.slim +0 -7
  43. data/lib/generators/rolemodel/mcp/templates/config/initializers/doorkeeper.rb +0 -537
  44. data/lib/generators/rolemodel/mcp/templates/spec/mcp/prompts/sample_spec.rb +0 -15
  45. data/lib/generators/rolemodel/mcp/templates/spec/mcp/resources/controller_spec.rb +0 -16
  46. data/lib/generators/rolemodel/mcp/templates/spec/mcp/resources/docs_controller_spec.rb +0 -55
  47. data/lib/generators/rolemodel/mcp/templates/spec/mcp/tools/sample_spec.rb +0 -15
  48. data/lib/generators/rolemodel/mcp/templates/spec/requests/mcp_controller_spec.rb +0 -84
  49. data/lib/generators/rolemodel/mcp/templates/spec/requests/oauth_registrations_controller_spec.rb +0 -62
  50. data/lib/generators/rolemodel/mcp/templates/spec/requests/well_known_controller_spec.rb +0 -30
  51. data/lib/generators/rolemodel/testing/rspec/templates/support/capybara_testid.rb +0 -5
  52. /data/lib/generators/rolemodel/testing/rspec/templates/{rails_helper.rb → spec/rails_helper.rb.tt} +0 -0
  53. /data/lib/generators/rolemodel/testing/rspec/templates/{spec_helper.rb → spec/spec_helper.rb.tt} +0 -0
  54. /data/lib/generators/rolemodel/testing/rspec/templates/{support/capybara_drivers.rb → spec/support/capybara_drivers.rb.tt} +0 -0
  55. /data/lib/generators/rolemodel/testing/rspec/templates/{support → spec/support}/helpers/action_cable_helper.rb +0 -0
  56. /data/lib/generators/rolemodel/testing/rspec/templates/{support → spec/support}/helpers/capybara_helper.rb +0 -0
  57. /data/lib/generators/rolemodel/testing/rspec/templates/{support → spec/support}/helpers/playwright_helper.rb +0 -0
  58. /data/lib/generators/rolemodel/testing/rspec/templates/{support → spec/support}/helpers/select_helper.rb +0 -0
  59. /data/lib/generators/rolemodel/testing/rspec/templates/{support → spec/support}/helpers/test_element_helper.rb +0 -0
  60. /data/lib/generators/rolemodel/testing/rspec/templates/{support/helpers.rb → spec/support/helpers.rb.tt} +0 -0
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rails_helper'
4
-
5
- RSpec.describe Prompts::Sample do
6
- describe '.template' do
7
- subject(:result) { described_class.template({}) }
8
-
9
- it 'has the correct description' do
10
- expect(result.messages.length).to eq(1)
11
- expect(result.messages.first.role).to eq('assistant')
12
- expect(result.description).to include('This is a sample prompt.')
13
- end
14
- end
15
- end
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rails_helper'
4
-
5
- RSpec.describe Resources::Controller do
6
- it 'returns error messages when invalid' do
7
- allow_any_instance_of(described_class).to receive(:valid?).and_return(false)
8
- allow_any_instance_of(described_class).to receive_message_chain(:errors, :full_messages).and_return(['Invalid params'])
9
- expect do
10
- described_class.call({ uri: 'docs://missing-doc' }, { current_user: nil })
11
- end.to raise_error(
12
- MCP::Server::RequestHandlerError,
13
- /Invalid params/
14
- )
15
- end
16
- end
@@ -1,55 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rails_helper'
4
-
5
- RSpec.describe Resources::DocsController do
6
- describe 'class methods' do
7
- it 'returns the correct values' do
8
- expect(described_class.schema).to eq('docs://')
9
- expect(described_class.mime_type).to eq('text/markdown')
10
- end
11
- end
12
-
13
- describe '.resource_list' do
14
- it 'registers the sample resource' do
15
- expect(described_class.resource_list.map(&:uri)).to contain_exactly('docs://SAMPLE_DOC.md')
16
- end
17
- end
18
-
19
- describe 'validations' do
20
- it 'is valid for a known docs resource' do
21
- controller = described_class.new('SAMPLE_DOC.md')
22
-
23
- expect(controller).to be_valid
24
- end
25
-
26
- it 'is invalid for an unknown docs resource' do
27
- controller = described_class.new('missing-doc')
28
-
29
- expect(controller).not_to be_valid
30
- expect(controller.errors[:file_path]).to eq(['Unknown docs resource: missing-doc'])
31
- end
32
-
33
- it 'is invalid when the mapped file is missing' do
34
- stub_const(
35
- 'Resources::DocsController::FILES',
36
- { 'SAMPLE_DOC.md' => Rails.root.join('app/mcp/resources/docs/missing.md') }.freeze
37
- )
38
-
39
- controller = described_class.new('SAMPLE_DOC.md')
40
-
41
- expect(controller).not_to be_valid
42
- expect(controller.errors[:file_path]).to eq(['Missing docs file for SAMPLE_DOC.md'])
43
- end
44
- end
45
-
46
- describe '#serve' do
47
- it 'returns the markdown for the requested docs resource' do
48
- controller = described_class.new('SAMPLE_DOC.md')
49
-
50
- content = controller.serve
51
-
52
- expect(content).to include('# Hello World')
53
- end
54
- end
55
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rails_helper'
4
-
5
- RSpec.describe Tools::Sample do
6
- describe '.call' do
7
- it 'returns a hash with expected fields' do
8
- result = described_class.call(name: 'Alice', server_context: {})
9
-
10
- expect(result).not_to be_error
11
-
12
- expect(result.structured_content).to have_key(:sample)
13
- end
14
- end
15
- end
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rails_helper'
4
-
5
- RSpec.describe 'MCPController', type: :request do
6
- let(:user) { create(:user, name: 'Jane Smith', active: true, employee: true) }
7
- let(:application) do
8
- Doorkeeper::Application.create!(
9
- name: 'MCP Test Client',
10
- redirect_uri: 'https://example.com/callback',
11
- scopes: 'mcp',
12
- )
13
- end
14
-
15
- describe 'POST /mcp' do
16
- let(:token) do
17
- Doorkeeper::AccessToken.create!(
18
- application: application,
19
- resource_owner_id: user.id,
20
- scopes: 'mcp',
21
- expires_in: 2.hours.to_i,
22
- )
23
- end
24
-
25
- let(:initialize_request) do
26
- {
27
- jsonrpc: '2.0',
28
- id: 1,
29
- method: 'initialize',
30
- params: {
31
- protocolVersion: '2025-11-25',
32
- capabilities: {},
33
- clientInfo: {
34
- name: 'rspec',
35
- version: '1.0',
36
- },
37
- },
38
- }
39
- end
40
-
41
- let(:headers) do
42
- {
43
- 'CONTENT_TYPE' => 'application/json',
44
- 'ACCEPT' => 'application/json, text/event-stream',
45
- }
46
- end
47
-
48
- let(:authorized_headers) do
49
- headers.merge('Authorization' => "Bearer #{token.token}")
50
- end
51
-
52
- it 'returns unauthorized when no bearer token is provided' do
53
- post '/mcp', params: initialize_request.to_json, headers: headers
54
-
55
- expect(response).to have_http_status(:unauthorized)
56
- expect(response.headers['WWW-Authenticate']).to include('resource_metadata=')
57
- end
58
-
59
- it 'returns forbidden when token does not include mcp scope' do
60
- token = Doorkeeper::AccessToken.create!(
61
- application: application,
62
- resource_owner_id: user.id,
63
- scopes: 'other',
64
- expires_in: 2.hours.to_i,
65
- )
66
-
67
- post '/mcp',
68
- params: initialize_request.to_json,
69
- headers: headers.merge('Authorization' => "Bearer #{token.token}")
70
-
71
- expect(response).to have_http_status(:forbidden)
72
- end
73
-
74
- it 'returns initialize response for a valid bearer token' do
75
- post '/mcp',
76
- params: initialize_request.to_json,
77
- headers: authorized_headers
78
-
79
- expect(response).to have_http_status(:ok)
80
- expect(response.parsed_body['result']).to be_present
81
- expect(response.parsed_body['result']['capabilities']).to include('tools')
82
- end
83
- end
84
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rails_helper'
4
-
5
- RSpec.describe 'OauthRegistrationsController', type: :request do
6
- describe 'POST /oauth/register' do
7
- let(:params) do
8
- {
9
- client_name: 'GitHub Copilot',
10
- redirect_uris: ['http://localhost:12345/callback'],
11
- grant_types: ['authorization_code'],
12
- response_types: ['code'],
13
- token_endpoint_auth_method: 'none',
14
- scope: 'mcp',
15
- }
16
- end
17
-
18
- it 'creates an OAuth application and returns client_id' do
19
- expect { post '/oauth/register', params: params, as: :json }
20
- .to change(Doorkeeper::Application, :count).by(1)
21
-
22
- expect(response).to have_http_status(:created)
23
- body = response.parsed_body
24
- expect(body['client_id']).to be_present
25
- expect(body['client_name']).to eq('GitHub Copilot')
26
- expect(body['redirect_uris']).to eq(['http://localhost:12345/callback'])
27
- expect(body['token_endpoint_auth_method']).to eq('none')
28
- expect(body).not_to have_key('client_secret')
29
-
30
- app = Doorkeeper::Application.last
31
- expect(app.name).to eq('GitHub Copilot')
32
- expect(app.redirect_uri).to eq('http://localhost:12345/callback')
33
- expect(app.scopes).to contain_exactly('mcp')
34
- expect(app).not_to be_confidential
35
- end
36
-
37
- it 'returns bad_request when redirect_uris is missing' do
38
- post '/oauth/register', params: { client_name: 'Test' }, as: :json
39
-
40
- expect(response).to have_http_status(:bad_request)
41
- expect(response.parsed_body['error']).to eq('invalid_client_metadata')
42
- end
43
-
44
- it 'creates a confidential client when token_endpoint_auth_method is not none' do
45
- params[:token_endpoint_auth_method] = 'client_secret_basic'
46
- post '/oauth/register', params: params, as: :json
47
-
48
- expect(response).to have_http_status(:created)
49
- body = response.parsed_body
50
- expect(body['client_secret']).to be_present
51
- expect(body['token_endpoint_auth_method']).to eq('client_secret_basic')
52
- end
53
-
54
- it 'allows loopback redirect URIs for native clients' do
55
- params[:redirect_uris] = ['http://127.0.0.1:33418/']
56
- post '/oauth/register', params: params, as: :json
57
-
58
- expect(response).to have_http_status(:created)
59
- expect(response.parsed_body['redirect_uris']).to eq(['http://127.0.0.1:33418/'])
60
- end
61
- end
62
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'rails_helper'
4
-
5
- RSpec.describe 'WellKnownController', type: :request do
6
- describe 'GET /.well-known/oauth-protected-resource' do
7
- it 'returns the MCP resource and authorization server' do
8
- get '/.well-known/oauth-protected-resource'
9
-
10
- expect(response).to have_http_status(:ok)
11
- body = response.parsed_body
12
- expect(body['resource']).to end_with('/mcp')
13
- expect(body['authorization_servers']).to be_an(Array)
14
- end
15
- end
16
-
17
- describe 'GET /.well-known/oauth-authorization-server' do
18
- it 'returns authorization server metadata with registration_endpoint' do
19
- get '/.well-known/oauth-authorization-server'
20
-
21
- expect(response).to have_http_status(:ok)
22
- body = response.parsed_body
23
- expect(body['registration_endpoint']).to end_with('/oauth/register')
24
- expect(body['authorization_endpoint']).to end_with('/oauth/authorize')
25
- expect(body['token_endpoint']).to end_with('/oauth/token')
26
- expect(body['code_challenge_methods_supported']).to include('S256')
27
- expect(body['scopes_supported']).to include('mcp')
28
- end
29
- end
30
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- Capybara.configure do |config|
4
- config.test_id = 'data-testid'
5
- end