• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MarkUsProject / Markus / 10928630900

18 Sep 2024 07:13PM UTC coverage: 51.86% (-39.7%) from 91.541%
10928630900

push

github

web-flow
Enable cron-based automatic syncing of LTI rosters (#7178)

574 of 1298 branches covered (44.22%)

Branch coverage included in aggregate %.

2 of 20 new or added lines in 4 files covered. (10.0%)

1886 existing lines in 60 files now uncovered.

3818 of 7171 relevant lines covered (53.24%)

76.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.62
/spec/support/lti_controller_examples.rb
1
shared_examples 'lti deployment controller' do
1✔
UNCOV
2
  let(:instructor) { create(:instructor) }
×
UNCOV
3
  let!(:client_id) { 'LMS defined ID' }
×
UNCOV
4
  let(:target_link_uri) { 'https://example.com/authorize_redirect' }
×
UNCOV
5
  let(:host) { 'https://test.host' }
×
UNCOV
6
  let(:state) { 'state_param' }
×
UNCOV
7
  let(:launch_params) do
×
UNCOV
8
    { client_id: 'LMS defined ID',
×
9
      login_hint: 'another opque string',
10
      lti_message_hint: 'opaque string',
11
      prompt: 'none',
12
      redirect_uri: 'https://example.com/authorize_redirect',
13
      response_mode: 'form_post',
14
      response_type: 'id_token',
15
      scope: 'openid' }
16
  end
UNCOV
17
  let(:redirect_uri) do
×
UNCOV
18
    root_uri = URI(root_url)
×
UNCOV
19
    root_uri.query = launch_params.to_query
×
UNCOV
20
    root_uri.to_s
×
21
  end
UNCOV
22
  describe '#launch' do
×
UNCOV
23
    context 'when launching with invalid parameters' do
×
UNCOV
24
      let(:lti_message_hint) { 'opaque string' }
×
UNCOV
25
      let(:login_hint) { 'another opque string' }
×
26

UNCOV
27
      it 'responds with unprocessable_entity if no parameters are passed' do
×
UNCOV
28
        request.headers['Referer'] = host
×
UNCOV
29
        post :launch, params: {}
×
UNCOV
30
        expect(subject).to respond_with(:unprocessable_entity)
×
31
      end
32

UNCOV
33
      it 'responds with unprocessable_entity if lti_message_hint is not passed' do
×
UNCOV
34
        request.headers['Referer'] = host
×
UNCOV
35
        post :launch, params: { client_id: client_id, target_link_uri: target_link_uri, login_hint: login_hint }
×
UNCOV
36
        expect(subject).to respond_with(:unprocessable_entity)
×
37
      end
38

UNCOV
39
      it 'responds with unprocessable_entity if client_id is not passed' do
×
UNCOV
40
        request.headers['Referer'] = host
×
UNCOV
41
        post :launch,
×
42
             params: { lti_message_hint: lti_message_hint, target_link_uri: target_link_uri, login_hint: login_hint }
UNCOV
43
        expect(subject).to respond_with(:unprocessable_entity)
×
44
      end
45

UNCOV
46
      it 'responds with unprocessable_entity if target_link_uri is not passed' do
×
UNCOV
47
        request.headers['Referer'] = host
×
UNCOV
48
        post :launch, params: { lti_message_hint: lti_message_hint, client_id: client_id, login_hint: login_hint }
×
UNCOV
49
        expect(subject).to respond_with(:unprocessable_entity)
×
50
      end
51

UNCOV
52
      it 'responds with unprocessable_entity if login_hint is not passed' do
×
UNCOV
53
        request.headers['Referer'] = host
×
UNCOV
54
        post :launch,
×
55
             params: { lti_message_hint: lti_message_hint, client_id: client_id, target_link_uri: target_link_uri }
UNCOV
56
        expect(subject).to respond_with(:unprocessable_entity)
×
57
      end
58

UNCOV
59
      context 'when all required params exist' do
×
UNCOV
60
        before do
×
UNCOV
61
          stub_request(:post, "https://test.host:443#{self.described_class::LMS_REDIRECT_ENDPOINT}")
×
62
            .with(
63
              body: hash_including(launch_params),
64
              headers: {
65
                'Accept' => '*/*',
66
                'Accept-Encoding' => 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3',
67
                'Content-Type' => 'application/x-www-form-urlencoded',
68
                'Host' => 'test.host',
69
                'User-Agent' => 'Ruby'
70
              }
71
            )
72
            .to_return(status: 302, body: 'stubbed response', headers: { location: redirect_uri })
73
        end
74

UNCOV
75
        context 'with correct parameters' do
×
UNCOV
76
          it 'redirects to the host auth url' do
×
UNCOV
77
            request.headers['Referer'] = host
×
UNCOV
78
            post :launch, params: { lti_message_hint: lti_message_hint,
×
79
                                    login_hint: login_hint,
80
                                    client_id: 'LMS defined ID', target_link_uri: target_link_uri }
UNCOV
81
            expect(response).to have_http_status(:found)
×
82
          end
83

UNCOV
84
          it 'sets the lti_launch cookie' do
×
UNCOV
85
            request.headers['Referer'] = host
×
UNCOV
86
            post :launch, params: { lti_message_hint: lti_message_hint,
×
87
                                    login_hint: login_hint,
88
                                    client_id: 'LMS defined ID', target_link_uri: target_link_uri }
UNCOV
89
            expect(cookies.encrypted[:lti_launch_data]).not_to be_nil
×
90
          end
91
        end
92
      end
93
    end
94
  end
95

UNCOV
96
  describe '#redirect_login' do
×
UNCOV
97
    let(:jwk_url) { "https://test.host:443#{self.class.described_class::LMS_JWK_ENDPOINT}" }
×
UNCOV
98
    let(:nonce) { rand(10 ** 30).to_s.rjust(30, '0') }
×
99

UNCOV
100
    before do
×
UNCOV
101
      lti_launch_data = {}
×
UNCOV
102
      lti_launch_data[:client_id] = client_id
×
UNCOV
103
      lti_launch_data[:iss] = host
×
UNCOV
104
      lti_launch_data[:nonce] = nonce
×
UNCOV
105
      lti_launch_data[:state] = session.id
×
UNCOV
106
      cookies.permanent.encrypted[:lti_launch_data] =
×
107
        { value: JSON.generate(lti_launch_data), expires: 1.hour.from_now }
108
    end
109

UNCOV
110
    it 'deletes the lti_launch_cookie' do
×
UNCOV
111
      request.headers['Referer'] = host
×
UNCOV
112
      post :redirect_login, params: {}
×
UNCOV
113
      expect(response.cookies).to include('lti_launch_data' => nil)
×
114
    end
115

UNCOV
116
    context 'post' do
×
UNCOV
117
      context 'with incorrect or missing parameters' do
×
UNCOV
118
        it 'redirects to an error page with no params' do
×
UNCOV
119
          request.headers['Referer'] = host
×
UNCOV
120
          post :redirect_login, params: {}
×
UNCOV
121
          expect(subject).to render_template('shared/http_status')
×
122
        end
123

UNCOV
124
        it 'redirects to an error page with a mismatched state' do
×
UNCOV
125
          request.headers['Referer'] = host
×
UNCOV
126
          post :redirect_login, params: { state: state, id_token: 'token' }
×
UNCOV
127
          expect(subject).to render_template('shared/http_status')
×
128
        end
129
      end
130

UNCOV
131
      context 'with correct parameters' do
×
UNCOV
132
        let(:payload) do
×
UNCOV
133
          { aud: client_id,
×
134
            iss: host,
135
            nonce: nonce,
136
            LtiDeployment::LTI_CLAIMS[:deployment_id] => 'some_deployment_id',
137
            LtiDeployment::LTI_CLAIMS[:context] => {
138
              label: 'csc108',
139
              title: 'test'
140
            },
141
            LtiDeployment::LTI_CLAIMS[:custom] => {
142
              course_id: 1,
143
              user_id: 1
144
            },
145
            LtiDeployment::LTI_CLAIMS[:user_launch_data] => {
146
              user_id: 'lti_user_id'
147
            } }
148
        end
UNCOV
149
        let(:pub_jwk) { JWT::JWK.new(OpenSSL::PKey::RSA.new(1024)) }
×
UNCOV
150
        let(:lti_jwt) { JWT.encode(payload, pub_jwk.keypair, 'RS256', { kid: pub_jwk.kid }) }
×
151

UNCOV
152
        before do
×
UNCOV
153
          session[:client_id] = client_id
×
UNCOV
154
          stub_request(:get, jwk_url).to_return(status: 200, body: { keys: [pub_jwk.export] }.to_json)
×
155
        end
156

UNCOV
157
        it 'successfully decodes the jwt and redirects' do
×
UNCOV
158
          request.headers['Referer'] = host
×
UNCOV
159
          post_as instructor, :redirect_login, params: { state: session.id.to_s, id_token: lti_jwt }
×
UNCOV
160
          expect(response).to redirect_to(choose_course_lti_deployment_path(LtiDeployment.first))
×
161
        end
162

UNCOV
163
        it 'successfully decodes the jwt and sets lti_course_id in the session' do
×
UNCOV
164
          request.headers['Referer'] = host
×
UNCOV
165
          post_as instructor, :redirect_login, params: { state: session.id.to_s, id_token: lti_jwt }
×
UNCOV
166
          expect(LtiDeployment.first.lms_course_id).to eq(1)
×
167
        end
168

UNCOV
169
        it 'successfully decodes the jwt and sets lti_course_name in the session' do
×
UNCOV
170
          request.headers['Referer'] = host
×
UNCOV
171
          post_as instructor, :redirect_login, params: { state: session.id.to_s, id_token: lti_jwt }
×
UNCOV
172
          expect(LtiDeployment.first.lms_course_name).to eq('test')
×
173
        end
174

UNCOV
175
        it 'successfully decodes the jwt and sets lti_course_label in the session' do
×
UNCOV
176
          request.headers['Referer'] = host
×
UNCOV
177
          post_as instructor, :redirect_login, params: { state: session.id.to_s, id_token: lti_jwt }
×
UNCOV
178
          expect(session[:lti_course_label]).to eq('csc108')
×
179
        end
180

UNCOV
181
        it 'successfully decodes the jwt and sets lti_user_id in the session' do
×
UNCOV
182
          request.headers['Referer'] = host
×
UNCOV
183
          post_as instructor, :redirect_login, params: { state: session.id.to_s, id_token: lti_jwt }
×
UNCOV
184
          expect(LtiUser.count).to eq(1)
×
185
        end
186

UNCOV
187
        it 'successfully creates a new lti object' do
×
UNCOV
188
          request.headers['Referer'] = host
×
UNCOV
189
          post_as instructor, :redirect_login, params: { state: session.id.to_s, id_token: lti_jwt }
×
UNCOV
190
          expect(LtiDeployment.count).to eq(1)
×
191
        end
192
      end
193
    end
194

UNCOV
195
    context 'get' do
×
UNCOV
196
      it 'returns an error if not logged in' do
×
UNCOV
197
        request.headers['Referer'] = host
×
UNCOV
198
        get :redirect_login
×
UNCOV
199
        expect(subject).to render_template('shared/http_status')
×
200
      end
201

UNCOV
202
      it 'returns an error if cookie is not present' do
×
UNCOV
203
        request.headers['Referer'] = host
×
UNCOV
204
        get_as instructor, :redirect_login
×
UNCOV
205
        expect(subject).to render_template('shared/http_status')
×
206
      end
207
    end
208

UNCOV
209
    context 'with a cookie' do
×
UNCOV
210
      let(:lti_data) do
×
UNCOV
211
        { host: 'example.com',
×
212
          client_id: 'client_id',
213
          deployment_id: '28:f97330a96452fc363a34e0ef6d8d0d3e9e1007d2',
214
          lms_course_name: 'Introduction to Computer Science',
215
          lms_course_label: 'CSC108',
216
          lms_course_id: 1,
217
          lti_user_id: 'user_id' }
218
      end
UNCOV
219
      let(:payload) do
×
220
        { aud: client_id,
×
221
          iss: 'https://example.com',
222
          LtiDeployment::LTI_CLAIMS[:deployment_id] => 'some_deployment_id',
223
          LtiDeployment::LTI_CLAIMS[:context] => {
224
            label: 'csc108',
225
            title: 'test'
226
          },
227
          LtiDeployment::LTI_CLAIMS[:custom] => {
228
            course_id: 1,
229
            user_id: 1
230
          } }
231
      end
UNCOV
232
      let(:pub_jwk) { JWT::JWK.new(OpenSSL::PKey::RSA.new(1024)) }
×
UNCOV
233
      let(:lti_jwt) { JWT.encode(payload, pub_jwk.keypair, 'RS256', { kid: pub_jwk.kid }) }
×
234

UNCOV
235
      before do
×
UNCOV
236
        cookies.permanent.encrypted[:lti_data] = { value: JSON.generate(lti_data), expires: 5.minutes.from_now }
×
UNCOV
237
        stub_request(:get, jwk_url).to_return(status: 200, body: { keys: [pub_jwk.export] }.to_json)
×
238
      end
239

UNCOV
240
      it 'successfully decodes the jwt and redirects' do
×
UNCOV
241
        request.headers['Referer'] = host
×
UNCOV
242
        get_as instructor, :redirect_login
×
UNCOV
243
        expect(response).to redirect_to(choose_course_lti_deployment_path(LtiDeployment.first))
×
244
      end
245

UNCOV
246
      it 'successfully decodes the jwt and sets lti_course_id in the session' do
×
UNCOV
247
        request.headers['Referer'] = host
×
UNCOV
248
        get_as instructor, :redirect_login
×
UNCOV
249
        expect(LtiDeployment.first.lms_course_id).to eq(1)
×
250
      end
251

UNCOV
252
      it 'successfully decodes the jwt and sets lti_course_name in the session' do
×
UNCOV
253
        request.headers['Referer'] = host
×
UNCOV
254
        get_as instructor, :redirect_login
×
UNCOV
255
        expect(LtiDeployment.first.lms_course_name).to eq('Introduction to Computer Science')
×
256
      end
257

UNCOV
258
      it 'successfully decodes the jwt and sets lti_course_label in the session' do
×
UNCOV
259
        request.headers['Referer'] = host
×
UNCOV
260
        get_as instructor, :redirect_login
×
UNCOV
261
        expect(session[:lti_course_label]).to eq('CSC108')
×
262
      end
263

UNCOV
264
      it 'successfully decodes the jwt and sets lti_user_id in the session' do
×
UNCOV
265
        request.headers['Referer'] = host
×
UNCOV
266
        get_as instructor, :redirect_login
×
UNCOV
267
        expect(LtiUser.count).to eq(1)
×
268
      end
269

UNCOV
270
      it 'successfully creates a new lti object' do
×
UNCOV
271
        request.headers['Referer'] = host
×
UNCOV
272
        get_as instructor, :redirect_login
×
UNCOV
273
        expect(LtiDeployment.count).to eq(1)
×
274
      end
275

UNCOV
276
      it 'deletes the data cookie' do
×
UNCOV
277
        request.headers['Referer'] = host
×
UNCOV
278
        get_as instructor, :redirect_login
×
UNCOV
279
        expect(response.cookies).to include('lti_data' => nil)
×
280
      end
281
    end
282
  end
283

UNCOV
284
  describe '#check_host' do
×
UNCOV
285
    before do
×
UNCOV
286
      request.env['HTTP_REFERER'] = root_url
×
287
    end
288

UNCOV
289
    it 'does not redirect to an error with a known host' do
×
UNCOV
290
      get_as instructor, :redirect_login
×
UNCOV
291
      expect(subject).to respond_with(:success)
×
292
    end
293

UNCOV
294
    it 'does redirect to an error with an unknown host' do
×
UNCOV
295
      request.headers['Referer'] = 'http://example.com'
×
UNCOV
296
      post_as instructor, :launch, params: { lti_message_hint: 'hint',
×
297
                                             login_hint: 'hint',
298
                                             client_id: 'LMS defined ID', target_link_uri: 'test.com' }
UNCOV
299
      expect(response).to have_http_status(:unprocessable_entity)
×
300
    end
301
  end
302
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc