tmc_testmycode_client/client/
api_v8.rs

1#![allow(dead_code)]
2
3//! Models the API of https://tmc.mooc.fi (https://testmycode.github.io/tmc-server/).
4
5use crate::{TestMyCodeClient, TestMyCodeClientError, request::*, response::*};
6use http::Method;
7use oauth2::TokenResponse;
8use reqwest::blocking::{
9    RequestBuilder, Response,
10    multipart::{Form, Part},
11};
12use serde::de::DeserializeOwned;
13use std::{
14    collections::HashMap,
15    io::{Read, Write},
16};
17use tmc_langs_plugins::Language;
18use tmc_langs_util::deserialize;
19use url::Url;
20
21pub enum PasteData {
22    WithoutMessage,
23    WithMessage(String),
24}
25
26pub enum ReviewData {
27    WithoutMessage,
28    WithMessage(String),
29}
30
31// joins the URL "tail" with the API url root from the client
32fn make_url(
33    client: &TestMyCodeClient,
34    tail: impl AsRef<str>,
35) -> Result<Url, TestMyCodeClientError> {
36    client
37        .0
38        .root_url
39        .join(tail.as_ref())
40        .map_err(|e| TestMyCodeClientError::UrlParse(tail.as_ref().to_string(), e))
41}
42
43// encodes a string so it can be used for a URL
44fn percent_encode(target: &str) -> String {
45    percent_encoding::utf8_percent_encode(target, percent_encoding::NON_ALPHANUMERIC).to_string()
46}
47
48// creates a request with the required TMC and authentication headers
49fn prepare_tmc_request(client: &TestMyCodeClient, method: Method, url: Url) -> RequestBuilder {
50    log::info!("{method} {url}");
51    let req = client.0.client.request(method, url).query(&[
52        ("client", &client.0.client_name),
53        ("client_version", &client.0.client_version),
54    ]);
55    if let Some(token) = &client.0.token {
56        req.bearer_auth(token.access_token().secret())
57    } else {
58        req
59    }
60}
61
62// checks a response for failure
63fn assert_success(response: Response, url: &Url) -> Result<Response, TestMyCodeClientError> {
64    let status = response.status();
65    if status.is_success() {
66        Ok(response)
67    } else if let Ok(err) = response.json::<ErrorResponse>() {
68        // failed and got an error json
69        let error = match (err.error, err.errors) {
70            (Some(err), Some(errs)) => format!("{}, {}", err, errs.join(",")),
71            (Some(err), None) => err,
72            (None, Some(errs)) => errs.join(","),
73            (None, None) => "".to_string(),
74        };
75        Err(TestMyCodeClientError::HttpError {
76            url: url.clone(),
77            status,
78            error,
79            obsolete_client: err.obsolete_client,
80        })
81    } else {
82        // failed and failed to parse error json, return generic HTTP error
83        Err(TestMyCodeClientError::HttpError {
84            url: url.clone(),
85            status,
86            error: status.to_string(),
87            obsolete_client: false,
88        })
89    }
90}
91
92fn assert_success_json<T: DeserializeOwned>(
93    res: Response,
94    url: &Url,
95) -> Result<T, TestMyCodeClientError> {
96    let res = assert_success(res, url)?
97        .bytes()
98        .map_err(TestMyCodeClientError::HttpReadResponse)?;
99    let json = deserialize::json_from_slice(&res)
100        .map_err(|e| TestMyCodeClientError::HttpJsonResponse(url.clone(), e))?;
101    Ok(json)
102}
103
104/// Converts a list of feedback answers to the format expected by the TMC server.
105pub fn prepare_feedback_form(feedback: Vec<FeedbackAnswer>) -> HashMap<String, String> {
106    let mut form = HashMap::new();
107    for (i, answer) in feedback.into_iter().enumerate() {
108        form.insert(
109            format!("answers[{i}][question_id]"),
110            answer.question_id.to_string(),
111        );
112        form.insert(format!("answers[{i}][answer]"), answer.answer);
113    }
114    form
115}
116
117/// Fetches data from the URL and writes it into the target.
118pub fn download(
119    client: &TestMyCodeClient,
120    url: Url,
121    target: &mut dyn Write,
122) -> Result<(), TestMyCodeClientError> {
123    let res = prepare_tmc_request(client, Method::GET, url.clone())
124        .send()
125        .map_err(|e| TestMyCodeClientError::ConnectionError(Method::GET, url.clone(), e))?;
126
127    let mut res = assert_success(res, &url)?;
128    let _bytes = res
129        .copy_to(target)
130        .map_err(TestMyCodeClientError::HttpWriteResponse)?;
131    Ok(())
132}
133
134/// Fetches JSON from the given URL and deserializes it into T.
135pub fn get_json<T: DeserializeOwned>(
136    client: &TestMyCodeClient,
137    url: Url,
138    params: &[(&str, String)],
139) -> Result<T, TestMyCodeClientError> {
140    let res = prepare_tmc_request(client, Method::GET, url.clone())
141        .query(params)
142        .send()
143        .map_err(|e| TestMyCodeClientError::ConnectionError(Method::GET, url.clone(), e))?;
144
145    let json = assert_success_json(res, &url)?;
146    Ok(json)
147}
148
149/// Posts the given form data to the given URL and deserializes the response to T.
150pub fn post_form<T: DeserializeOwned>(
151    client: &TestMyCodeClient,
152    url: Url,
153    form: &HashMap<String, String>,
154) -> Result<T, TestMyCodeClientError> {
155    let res = prepare_tmc_request(client, Method::POST, url.clone())
156        .form(form)
157        .send()
158        .map_err(|e| TestMyCodeClientError::ConnectionError(Method::GET, url.clone(), e))?;
159
160    let json = assert_success_json(res, &url)?;
161    Ok(json)
162}
163
164/// get /api/v8/application/{client_name}/credentials
165/// Fetches oauth2 credentials info.
166pub fn get_credentials(client: &TestMyCodeClient) -> Result<Credentials, TestMyCodeClientError> {
167    let client_name = &client.0.client_name;
168    let url = make_url(
169        client,
170        format!("/api/v8/application/{client_name}/credentials"),
171    )?;
172    get_json(client, url, &[])
173}
174
175/// get /api/v8/core/submissions/{submission_id}
176/// Checks the submission processing status from the given URL.
177pub fn get_submission(
178    client: &TestMyCodeClient,
179    submission_id: u32,
180) -> Result<SubmissionProcessingStatus, TestMyCodeClientError> {
181    let url = make_url(client, format!("/api/v8/core/submissions/{submission_id}"))?;
182    get_json(client, url, &[])
183}
184
185pub mod user {
186    use super::*;
187
188    /// get /api/v8/users/{user_id}
189    /// Returns the user's username, email, and administrator status by user id
190    pub fn get(client: &TestMyCodeClient, user_id: u32) -> Result<User, TestMyCodeClientError> {
191        let url = make_url(client, format!("/api/v8/users/{user_id}"))?;
192        get_json(client, url, &[])
193    }
194
195    /// get /api/v8/users/current
196    /// Returns the current user's username, email, and administrator status
197    pub fn get_current(client: &TestMyCodeClient) -> Result<User, TestMyCodeClientError> {
198        let url = make_url(client, "/api/v8/users/current")?;
199        get_json(client, url, &[])
200    }
201
202    /// post /api/v8/users/basic_info_by_usernames
203    /// Requires admin.
204    /// Find all users' basic infos with the posted json array of usernames
205    pub fn get_basic_info_by_usernames(
206        client: &TestMyCodeClient,
207        usernames: &[String],
208    ) -> Result<Vec<User>, TestMyCodeClientError> {
209        let url = make_url(client, "/api/v8/users/basic_info_by_usernames")?;
210        let mut username_map = HashMap::new();
211        username_map.insert("usernames", usernames);
212        let res = prepare_tmc_request(client, Method::POST, url.clone())
213            .json(&username_map)
214            .send()
215            .map_err(|e| TestMyCodeClientError::ConnectionError(Method::POST, url.clone(), e))?;
216
217        let json = assert_success_json(res, &url)?;
218        Ok(json)
219    }
220
221    /// post /api/v8/users/basic_info_by_emails
222    /// Requires admin.
223    /// Find all users' basic infos with the posted json array of emails
224    pub fn get_basic_info_by_emails(
225        client: &TestMyCodeClient,
226        emails: &[String],
227    ) -> Result<Vec<User>, TestMyCodeClientError> {
228        let url = make_url(client, "/api/v8/users/basic_info_by_emails")?;
229        let mut email_map = HashMap::new();
230        email_map.insert("emails", emails);
231        let res = prepare_tmc_request(client, Method::POST, url.clone())
232            .json(&email_map)
233            .send()
234            .map_err(|e| TestMyCodeClientError::ConnectionError(Method::POST, url.clone(), e))?;
235
236        let json = assert_success_json(res, &url)?;
237        Ok(json)
238    }
239}
240
241pub mod course {
242    use super::*;
243
244    /// get /api/v8/courses/{course_id}
245    /// Returns the course's information in a json format. Course is searched by id
246    pub fn get_by_id(
247        client: &TestMyCodeClient,
248        course_id: u32,
249    ) -> Result<CourseData, TestMyCodeClientError> {
250        let url = make_url(client, format!("/api/v8/courses/{course_id}"))?;
251        get_json(client, url, &[])
252    }
253
254    /// get /api/v8/org/{organization_slug}/courses/{course_name}
255    /// Returns the course's information in a json format. Course is searched by organization slug and course name
256    pub fn get(
257        client: &TestMyCodeClient,
258        organization_slug: &str,
259        course_name: &str,
260    ) -> Result<CourseData, TestMyCodeClientError> {
261        let url = make_url(
262            client,
263            format!(
264                "/api/v8/org/{}/courses/{}",
265                percent_encode(organization_slug),
266                percent_encode(course_name)
267            ),
268        )?;
269        get_json(client, url, &[])
270    }
271}
272
273pub mod point {
274    use super::*;
275
276    /// get /api/v8/courses/{course_id}/points
277    /// Returns the course's points in a json format. Course is searched by id
278    pub fn get_course_points_by_id(
279        client: &TestMyCodeClient,
280        course_id: u32,
281    ) -> Result<Vec<CourseDataExercisePoint>, TestMyCodeClientError> {
282        let url = make_url(client, format!("/api/v8/courses/{course_id}/points"))?;
283        get_json(client, url, &[])
284    }
285
286    /// get /api/v8/courses/{course_id}/exercises/{exercise_name}/points
287    /// Returns all the awarded points of an excercise for all users
288    pub fn get_exercise_points_by_id(
289        client: &TestMyCodeClient,
290        course_id: u32,
291        exercise_name: &str,
292    ) -> Result<Vec<CourseDataExercisePoint>, TestMyCodeClientError> {
293        let url = make_url(
294            client,
295            format!(
296                "/api/v8/courses/{}/exercises/{}/points",
297                course_id,
298                percent_encode(exercise_name)
299            ),
300        )?;
301        get_json(client, url, &[])
302    }
303
304    /// get /api/v8/courses/{course_id}/exercises/{exercise_name}/users/{user_id}/points
305    /// Returns all the awarded points of an excercise for the specified user
306    pub fn get_exercise_points_for_user_by_id(
307        client: &TestMyCodeClient,
308        course_id: u32,
309        exercise_name: &str,
310        user_id: u32,
311    ) -> Result<Vec<CourseDataExercisePoint>, TestMyCodeClientError> {
312        let url = make_url(
313            client,
314            format!(
315                "/api/v8/courses/{}/exercises/{}/users/{}/points",
316                course_id,
317                percent_encode(exercise_name),
318                user_id
319            ),
320        )?;
321        get_json(client, url, &[])
322    }
323
324    /// get /api/v8/courses/{course_id}/exercises/{exercise_name}/users/current/points
325    /// Returns all the awarded points of an excercise for current user
326    pub fn get_exercise_points_for_current_user_by_id(
327        client: &TestMyCodeClient,
328        course_id: u32,
329        exercise_name: &str,
330    ) -> Result<Vec<CourseDataExercisePoint>, TestMyCodeClientError> {
331        let url = make_url(
332            client,
333            format!(
334                "/api/v8/courses/{}/exercises/{}/users/current/points",
335                course_id,
336                percent_encode(exercise_name)
337            ),
338        )?;
339        get_json(client, url, &[])
340    }
341
342    /// get /api/v8/courses/{course_id}/users/{user_id}/points
343    /// Returns the given user's points from the course in a json format. Course is searched by id
344    pub fn get_course_points_for_user_by_id(
345        client: &TestMyCodeClient,
346        course_id: u32,
347        user_id: u32,
348    ) -> Result<Vec<CourseDataExercisePoint>, TestMyCodeClientError> {
349        let url = make_url(
350            client,
351            format!("/api/v8/courses/{course_id}/users/{user_id}/points"),
352        )?;
353        get_json(client, url, &[])
354    }
355
356    /// get /api/v8/courses/{course_id}/users/current/points
357    /// Returns the current user's points from the course in a json format. Course is searched by id
358    pub fn get_course_points_for_current_user_by_id(
359        client: &TestMyCodeClient,
360        course_id: u32,
361    ) -> Result<Vec<CourseDataExercisePoint>, TestMyCodeClientError> {
362        let url = make_url(
363            client,
364            format!("/api/v8/courses/{course_id}/users/current/points"),
365        )?;
366        get_json(client, url, &[])
367    }
368
369    /// get /api/v8/org/{organization_slug}/courses/{course_name}/points
370    /// Returns the course's points in a json format. Course is searched by name
371    pub fn get_course_points(
372        client: &TestMyCodeClient,
373        organization_slug: &str,
374        course_name: &str,
375    ) -> Result<Vec<CourseDataExercisePoint>, TestMyCodeClientError> {
376        let url = make_url(
377            client,
378            format!(
379                "/api/v8/org/{}/courses/{}/points",
380                percent_encode(organization_slug),
381                percent_encode(course_name)
382            ),
383        )?;
384        get_json(client, url, &[])
385    }
386
387    /* unimplemented, "This feature is only for MOOC-organization's 2019 programming MOOC"
388    /// get /api/v8/org/{organization_slug}/courses/{course_name}/eligible_students
389    /// Returns all users from the course who have at least 90% of every part's points and are applying for study right, in a json format. Course is searched by name, only 2019 programming mooc course is valid
390    pub fn get_course_eligible_students(
391        client: &TestMyCodeClient,
392        organization_slug: &str,
393        course_name: &str,
394    ) -> Result<(), ClientError> {
395        let url = make_url(
396            client,
397            format!(
398                "/api/v8/org/{}/courses/{}/eligible_students",
399                percent_encode(organization_slug),
400                percent_encode(course_name)
401            ),
402        )?;
403        todo!()
404    }
405    */
406
407    /// get /api/v8/org/{organization_slug}/courses/{course_name}/exercises/{exercise_name}/points
408    /// Returns all the awarded points of an excercise for all users
409    pub fn get_exercise_points(
410        client: &TestMyCodeClient,
411        organization_slug: &str,
412        course_name: &str,
413        exercise_name: &str,
414    ) -> Result<Vec<CourseDataExercisePoint>, TestMyCodeClientError> {
415        let url = make_url(
416            client,
417            format!(
418                "/api/v8/org/{}/courses/{}/exercises/{}/points",
419                percent_encode(organization_slug),
420                percent_encode(course_name),
421                percent_encode(exercise_name)
422            ),
423        )?;
424        get_json(client, url, &[])
425    }
426
427    /* does not seem to work
428    /// get /api/v8/org/{organization_slug}/courses/{course_name}/exercises/{exercise_name}/users/current/points
429    /// Returns all the awarded points of an excercise for current user
430    pub fn get_exercise_points_for_current_user(
431        client: &TestMyCodeClient,
432        organization_slug: &str,
433        course_name: &str,
434        exercise_name: &str,
435    ) -> Result<Vec<CourseDataExercisePoint>, ClientError> {
436        let url = make_url(
437            client,
438            format!(
439                "/api/v8/org/{}/courses/{}/exercises/{}/users/current/points",
440                percent_encode(organization_slug),
441                percent_encode(course_name),
442                percent_encode(exercise_name)
443            ),
444        )?;
445        get_json(client, url, &[])
446    }
447    */
448
449    /* does not seem to work
450    /// get /api/v8/org/{organization_slug}/courses/{course_name}/exercises/{exercise_name}/users/{user_id}/points
451    /// Returns all the awarded points of an excercise for the specified user
452    pub fn get_exercise_points_for_user(
453        client: &TestMyCodeClient,
454        organization_slug: &str,
455        course_name: &str,
456        exercise_name: &str,
457        user_id: u32,
458    ) -> Result<Vec<CourseDataExercisePoint>, ClientError> {
459        let url = make_url(
460            client,
461            format!(
462                "/api/v8/org/{}/courses/{}/exercises/{}/users/{}/points",
463                percent_encode(organization_slug),
464                percent_encode(course_name),
465                percent_encode(exercise_name),
466                user_id
467            ),
468        )?;
469        get_json(client, url, &[])
470    }
471    */
472
473    /// get /api/v8/org/{organization_slug}/courses/{course_name}/users/{user_id}/points
474    /// Returns the given user's points from the course in a json format. Course is searched by name
475    pub fn get_course_points_for_user(
476        client: &TestMyCodeClient,
477        organization_slug: &str,
478        course_name: &str,
479        user_id: u32,
480    ) -> Result<Vec<CourseDataExercisePoint>, TestMyCodeClientError> {
481        let url = make_url(
482            client,
483            format!(
484                "/api/v8/org/{}/courses/{}/users/{}/points",
485                percent_encode(organization_slug),
486                percent_encode(course_name),
487                user_id
488            ),
489        )?;
490        get_json(client, url, &[])
491    }
492
493    /// get /api/v8/org/{organization_slug}/courses/{course_name}/users/current/points
494    /// Returns the current user's points from the course in a json format. Course is searched by name
495    pub fn get_course_points_for_current_user(
496        client: &TestMyCodeClient,
497        organization_slug: &str,
498        course_name: &str,
499    ) -> Result<Vec<CourseDataExercisePoint>, TestMyCodeClientError> {
500        let url = make_url(
501            client,
502            format!(
503                "/api/v8/org/{}/courses/{}/users/current/points",
504                percent_encode(organization_slug),
505                percent_encode(course_name),
506            ),
507        )?;
508        get_json(client, url, &[])
509    }
510}
511
512pub mod submission {
513    use super::*;
514
515    /// get /api/v8/courses/{course_id}/submissions
516    /// Returns the submissions visible to the user in a json format
517    pub fn get_course_submissions_by_id(
518        client: &TestMyCodeClient,
519        course_id: u32,
520    ) -> Result<Vec<Submission>, TestMyCodeClientError> {
521        let url = make_url(client, format!("/api/v8/courses/{course_id}/submissions"))?;
522        get_json(client, url, &[])
523    }
524
525    /// get /api/v8/courses/{course_id}/submissions/last_hour
526    /// Returns submissions to the course in the latest hour
527    pub fn get_course_submissions_for_last_hour(
528        client: &TestMyCodeClient,
529        course_id: u32,
530    ) -> Result<Vec<u32>, TestMyCodeClientError> {
531        let url = make_url(
532            client,
533            format!("/api/v8/courses/{course_id}/submissions/last_hour"),
534        )?;
535        get_json(client, url, &[])
536    }
537
538    /// get /api/v8/courses/{course_id}/users/{user_id}/submissions
539    /// Returns the submissions visible to the user in a json format
540    pub fn get_course_submissions_for_user_by_id(
541        client: &TestMyCodeClient,
542        course_id: u32,
543        user_id: u32,
544    ) -> Result<Vec<Submission>, TestMyCodeClientError> {
545        let url = make_url(
546            client,
547            format!("/api/v8/courses/{course_id}/users/{user_id}/submissions"),
548        )?;
549        get_json(client, url, &[])
550    }
551
552    /// get /api/v8/courses/{course_id}/users/current/submissions
553    /// Returns the user's own submissions in a json format
554    pub fn get_course_submissions_for_current_user_by_id(
555        client: &TestMyCodeClient,
556        course_id: u32,
557    ) -> Result<Vec<Submission>, TestMyCodeClientError> {
558        let url = make_url(
559            client,
560            format!("/api/v8/courses/{course_id}/users/current/submissions",),
561        )?;
562        get_json(client, url, &[])
563    }
564
565    /// get api/v8/exercises/{exercise_id}/users/{user_id}/submissions
566    /// Returns the submissions visible to the user in a json format
567    pub fn get_exercise_submissions_for_user(
568        client: &TestMyCodeClient,
569        exercise_id: u32,
570        user_id: u32,
571    ) -> Result<Vec<Submission>, TestMyCodeClientError> {
572        let url = make_url(
573            client,
574            format!("/api/v8/exercises/{exercise_id}/users/{user_id}/submissions"),
575        )?;
576        get_json(client, url, &[])
577    }
578
579    /// get api/v8/exercises/{exercise_id}/users/current/submissions
580    /// Returns the current user's submissions for the exercise in a json format. The exercise is searched by id.
581    pub fn get_exercise_submissions_for_current_user(
582        client: &TestMyCodeClient,
583        exercise_id: u32,
584    ) -> Result<Vec<Submission>, TestMyCodeClientError> {
585        let url = make_url(
586            client,
587            format!("/api/v8/exercises/{exercise_id}/users/current/submissions"),
588        )?;
589        get_json(client, url, &[])
590    }
591
592    /// get /api/v8/org/{organization_slug}/courses/{course_name}/submissions
593    /// Returns the submissions visible to the user in a json format
594    pub fn get_course_submissions(
595        client: &TestMyCodeClient,
596        organization_slug: &str,
597        course_name: &str,
598    ) -> Result<Vec<Submission>, TestMyCodeClientError> {
599        let url = make_url(
600            client,
601            format!(
602                "/api/v8/org/{}/courses/{}/submissions",
603                percent_encode(organization_slug),
604                percent_encode(course_name)
605            ),
606        )?;
607        get_json(client, url, &[])
608    }
609
610    /// get /api/v8/org/{organization_slug}/courses/{course_name}/users/{user_id}/submissions
611    /// Returns the submissions visible to the user in a json format
612    pub fn get_course_submissions_for_user(
613        client: &TestMyCodeClient,
614        organization_slug: &str,
615        course_name: &str,
616        user_id: u32,
617    ) -> Result<Vec<Submission>, TestMyCodeClientError> {
618        let url = make_url(
619            client,
620            format!(
621                "/api/v8/org/{}/courses/{}/users/{}/submissions",
622                percent_encode(organization_slug),
623                percent_encode(course_name),
624                user_id
625            ),
626        )?;
627        get_json(client, url, &[])
628    }
629
630    /// get /api/v8/org/{organization_slug}/courses/{course_name}/users/current/submissions
631    /// Returns the user's own submissions in a json format
632    pub fn get_course_submissions_for_current_user(
633        client: &TestMyCodeClient,
634        organization_slug: &str,
635        course_name: &str,
636    ) -> Result<Vec<Submission>, TestMyCodeClientError> {
637        let url = make_url(
638            client,
639            format!(
640                "/api/v8/org/{}/courses/{}/users/current/submissions",
641                percent_encode(organization_slug),
642                percent_encode(course_name),
643            ),
644        )?;
645        get_json(client, url, &[])
646    }
647}
648
649pub mod exercise {
650    use super::*;
651
652    /// get /api/v8/courses/{course_id}/exercises
653    /// Returns all exercises of the course as json. Course is searched by id
654    pub fn get_course_exercises_by_id(
655        client: &TestMyCodeClient,
656        course_id: u32,
657    ) -> Result<Vec<CourseExercise>, TestMyCodeClientError> {
658        let url = make_url(client, format!("/api/v8/courses/{course_id}/exercises"))?;
659        get_json(client, url, &[])
660    }
661
662    /// get api/v8/exercises/{exercise_id}/users/{user_id}/submissions
663    /// Returns the submissions visible to the user in a json format
664    pub fn get_exercise_submissions_for_user(
665        client: &TestMyCodeClient,
666        exercise_id: u32,
667        user_id: u32,
668    ) -> Result<Vec<Submission>, TestMyCodeClientError> {
669        let url = make_url(
670            client,
671            format!("/api/v8/exercises/{exercise_id}/users/{user_id}/submissions"),
672        )?;
673        get_json(client, url, &[])
674    }
675
676    /// get api/v8/exercises/{exercise_id}/users/current/submissions
677    /// Returns the current user's submissions for the exercise in a json format. The exercise is searched by id.
678    pub fn get_exercise_submissions_for_current_user(
679        client: &TestMyCodeClient,
680        exercise_id: u32,
681    ) -> Result<Vec<Submission>, TestMyCodeClientError> {
682        let url = make_url(
683            client,
684            format!("/api/v8/exercises/{exercise_id}/users/current/submissions"),
685        )?;
686        get_json(client, url, &[])
687    }
688
689    /// get /api/v8/org/{organization_slug}/courses/{course_name}/exercises
690    /// Returns all exercises of the course as json. Course is searched by name
691    pub fn get_course_exercises(
692        client: &TestMyCodeClient,
693        organization_slug: &str,
694        course_name: &str,
695    ) -> Result<Vec<CourseDataExercise>, TestMyCodeClientError> {
696        let url = make_url(
697            client,
698            format!(
699                "/api/v8/org/{}/courses/{}/exercises",
700                percent_encode(organization_slug),
701                percent_encode(course_name)
702            ),
703        )?;
704        get_json(client, url, &[])
705    }
706
707    /// get /api/v8/org/{organization_slug}/courses/{course_name}/exercises/{exercise_name}/download
708    /// Download the exercise as a zip file
709    pub fn download_course_exercise(
710        client: &TestMyCodeClient,
711        organization_slug: &str,
712        course_name: &str,
713        exercise_name: &str,
714        target: &mut dyn Write,
715    ) -> Result<(), TestMyCodeClientError> {
716        let url = make_url(
717            client,
718            format!(
719                "/api/v8/org/{}/courses/{}/exercises/{}/download",
720                percent_encode(organization_slug),
721                percent_encode(course_name),
722                percent_encode(exercise_name)
723            ),
724        )?;
725        download(client, url, target)
726    }
727}
728
729pub mod organization {
730    use super::*;
731
732    /// get /api/v8/org.json
733    /// Returns a list of all organizations
734    pub fn get_organizations(
735        client: &TestMyCodeClient,
736    ) -> Result<Vec<Organization>, TestMyCodeClientError> {
737        let url = make_url(client, "/api/v8/org.json")?;
738        get_json(client, url, &[])
739    }
740
741    /// get /api/v8/org/{organization_slug}.json
742    /// Returns a json representation of the organization
743    pub fn get_organization(
744        client: &TestMyCodeClient,
745        organization_slug: &str,
746    ) -> Result<Organization, TestMyCodeClientError> {
747        let url = make_url(
748            client,
749            format!("/api/v8/org/{}.json", percent_encode(organization_slug)),
750        )?;
751        get_json(client, url, &[])
752    }
753}
754
755pub mod core {
756    use super::*;
757
758    /// get /api/v8/core/courses/{course_id}
759    /// Returns the course details in a json format. Course is searched by id
760    pub fn get_course(
761        client: &TestMyCodeClient,
762        course_id: u32,
763    ) -> Result<CourseDetails, TestMyCodeClientError> {
764        let url = make_url(client, format!("/api/v8/core/courses/{course_id}"))?;
765        get_json(client, url, &[])
766    }
767
768    /// get /api/v8/core/courses/{course_id}/reviews
769    /// Returns the course's review information for current user's submissions in a json format. Course is searched by id
770    pub fn get_course_reviews(
771        client: &TestMyCodeClient,
772        course_id: u32,
773    ) -> Result<Vec<Review>, TestMyCodeClientError> {
774        let url = make_url(client, format!("/api/v8/core/courses/{course_id}/reviews"))?;
775        get_json(client, url, &[])
776    }
777
778    /// put /api/v8/core/courses/{course_id}/reviews/{review_id}
779    /// Update existing review.
780    /// Review text can be updated by using `review_body`.
781    /// Review can be marked as read or unread by setting `mark_as_read`.
782    pub fn update_course_review(
783        client: &TestMyCodeClient,
784        course_id: u32,
785        review_id: u32,
786        review_body: Option<String>,
787        mark_as_read: Option<bool>,
788    ) -> Result<(), TestMyCodeClientError> {
789        let url = make_url(
790            client,
791            format!("/api/v8/core/courses/{course_id}/reviews/{review_id}"),
792        )?;
793
794        let mut form = HashMap::new();
795        if let Some(review_body) = review_body {
796            form.insert("review[review_body]", review_body);
797        }
798        if let Some(mark_as_read) = mark_as_read {
799            if mark_as_read {
800                form.insert("mark_as_read", "true".to_string());
801            } else {
802                form.insert("mark_as_unread", "true".to_string());
803            }
804        }
805        let res = prepare_tmc_request(client, Method::PUT, url.clone())
806            .form(&form)
807            .send()
808            .map_err(|e| TestMyCodeClientError::ConnectionError(Method::PUT, url.clone(), e))?;
809
810        assert_success(res, &url)?;
811        Ok(())
812    }
813
814    /// post /api/v8/core/courses/{course_id}/unlock
815    /// Untested.
816    /// Unlocks the courses exercises
817    pub fn unlock_course(
818        client: &TestMyCodeClient,
819        course_id: u32,
820    ) -> Result<(), TestMyCodeClientError> {
821        let url = make_url(client, format!("/api/v8/core/courses/{course_id}/unlock"))?;
822        let res = prepare_tmc_request(client, Method::POST, url.clone())
823            .send()
824            .map_err(|e| TestMyCodeClientError::ConnectionError(Method::POST, url.clone(), e))?;
825
826        assert_success(res, &url)?;
827        Ok(())
828    }
829
830    /// get /api/v8/core/exercises/{exercise_id}/download
831    /// Download the exercise as a zip file
832    pub fn download_exercise(
833        client: &TestMyCodeClient,
834        exercise_id: u32,
835        target: &mut dyn Write,
836    ) -> Result<(), TestMyCodeClientError> {
837        let url = make_url(
838            client,
839            format!("/api/v8/core/exercises/{exercise_id}/download"),
840        )?;
841        download(client, url, target)
842    }
843
844    /// get /api/v8/core/exercises/{exercise_id}
845    /// Returns information about exercise and its submissions.
846    pub fn get_exercise(
847        client: &TestMyCodeClient,
848        exercise_id: u32,
849    ) -> Result<ExerciseDetails, TestMyCodeClientError> {
850        let url = make_url(client, format!("/api/v8/core/exercises/{exercise_id}"))?;
851        get_json(client, url, &[])
852    }
853
854    /// get /api/v8/core/exercises/details
855    /// Fetch multiple exercise details as query parameters.
856    pub fn get_exercise_details(
857        client: &TestMyCodeClient,
858        exercises: &[u32],
859    ) -> Result<Vec<ExercisesDetails>, TestMyCodeClientError> {
860        let url = make_url(client, "/api/v8/core/exercises/details")?;
861        let exercise_ids = (
862            "ids",
863            exercises
864                .iter()
865                .map(u32::to_string)
866                .collect::<Vec<_>>()
867                .join(","),
868        );
869
870        let res: ExercisesDetailsWrapper = get_json(client, url, &[exercise_ids])?;
871        Ok(res.exercises)
872    }
873
874    /// get /api/v8/core/exercises/{exercise_id}/solution/download
875    /// Download the solution for an exercise as a zip file
876    pub fn download_exercise_solution(
877        client: &TestMyCodeClient,
878        exercise_id: u32,
879        target: &mut dyn Write,
880    ) -> Result<(), TestMyCodeClientError> {
881        let url = make_url(
882            client,
883            format!("/api/v8/core/exercises/{exercise_id}/solution/download"),
884        )?;
885        download(client, url, target)
886    }
887
888    /// post /api/v8/core/exercises/{exercise_id}/submissions
889    /// Create submission from a zip file
890    pub fn submit_exercise(
891        client: &TestMyCodeClient,
892        exercise_id: u32,
893        submission_zip: impl Read + Send + Sync + 'static,
894        submit_paste: Option<PasteData>,
895        submit_for_review: Option<ReviewData>,
896        locale: Option<Language>,
897    ) -> Result<NewSubmission, TestMyCodeClientError> {
898        let url = make_url(
899            client,
900            format!("/api/v8/core/exercises/{exercise_id}/submissions"),
901        )?;
902
903        let mut form = Form::new();
904        form = form.part(
905            "submission[file]",
906            Part::reader(submission_zip).file_name("submission.zip"),
907        );
908
909        if let Some(submit_paste) = submit_paste {
910            form = form.text("paste", "1");
911            if let PasteData::WithMessage(message) = submit_paste {
912                form = form.text("message_for_paste", message);
913            }
914        }
915        if let Some(submit_for_review) = submit_for_review {
916            form = form.text("request_review", "1");
917            if let ReviewData::WithMessage(message) = submit_for_review {
918                form = form.text("message_for_reviewer", message);
919            }
920        }
921
922        if let Some(locale) = locale {
923            form = form.text("error_msg_locale", locale.to_639_3());
924        }
925
926        let res = prepare_tmc_request(client, Method::POST, url.clone())
927            .multipart(form)
928            .send()
929            .map_err(|e| TestMyCodeClientError::ConnectionError(Method::POST, url.clone(), e))?;
930
931        let json = assert_success_json(res, &url)?;
932        Ok(json)
933    }
934
935    /// get /api/v8/core/org/{organization_slug}/courses
936    /// Returns an array containing each course's collection of links
937    pub fn get_organization_courses(
938        client: &TestMyCodeClient,
939        organization_slug: &str,
940    ) -> Result<Vec<Course>, TestMyCodeClientError> {
941        let url = make_url(
942            client,
943            format!(
944                "/api/v8/core/org/{}/courses",
945                percent_encode(organization_slug)
946            ),
947        )?;
948        get_json(client, url, &[])
949    }
950
951    /// get /api/v8/core/submissions/{submission_id}/download
952    /// Download the submission as a zip file
953    pub fn download_submission(
954        client: &TestMyCodeClient,
955        submission_id: u32,
956        target: &mut dyn Write,
957    ) -> Result<(), TestMyCodeClientError> {
958        let url = make_url(
959            client,
960            format!("/api/v8/core/submissions/{submission_id}/download"),
961        )?;
962        download(client, url, target)
963    }
964
965    /// post /api/v8/core/submissions/{submission_id}/feedback
966    /// Submits a feedback for submission
967    pub fn post_submission_feedback(
968        client: &TestMyCodeClient,
969        submission_id: u32,
970        feedback: Vec<FeedbackAnswer>,
971    ) -> Result<SubmissionFeedbackResponse, TestMyCodeClientError> {
972        let url = make_url(
973            client,
974            format!("/api/v8/core/submissions/{submission_id}/feedback"),
975        )?;
976
977        let form = prepare_feedback_form(feedback);
978        let res = prepare_tmc_request(client, Method::POST, url.clone())
979            .form(&form)
980            .send()
981            .map_err(|e| TestMyCodeClientError::ConnectionError(Method::POST, url.clone(), e))?;
982
983        let json = assert_success_json(res, &url)?;
984        Ok(json)
985    }
986
987    /// post /api/v8/core/submissions/{submission_id}/reviews
988    /// Submits a review for the submission
989    pub fn post_submission_review(
990        client: &TestMyCodeClient,
991        submission_id: u32,
992        review: String,
993        // review_points: &[String], doesn't work yet
994    ) -> Result<(), TestMyCodeClientError> {
995        let url = make_url(
996            client,
997            format!("/api/v8/core/submissions/{submission_id}/reviews"),
998        )?;
999
1000        let mut form = HashMap::new();
1001        form.insert("review[review_body]".to_string(), review);
1002
1003        /*
1004        for point in review_points {
1005            form.insert(format!("review[points][{}]", point), "".to_string());
1006        }
1007        */
1008
1009        let res = prepare_tmc_request(client, Method::POST, url.clone())
1010            .form(&form)
1011            .send()
1012            .map_err(|e| TestMyCodeClientError::ConnectionError(Method::POST, url.clone(), e))?;
1013
1014        assert_success(res, &url)?;
1015        Ok(())
1016    }
1017}
1018
1019#[cfg(test)]
1020#[allow(clippy::unwrap_used)]
1021mod test {
1022    use super::{super::TestMyCodeClient, *};
1023    use mockito::{Matcher, Mock, Server};
1024    use std::io::{Cursor, Seek};
1025
1026    fn init() {
1027        use log::*;
1028        use simple_logger::*;
1029        let _ = SimpleLogger::new().with_level(LevelFilter::Debug).init();
1030    }
1031
1032    fn make_client(server: &Server) -> TestMyCodeClient {
1033        TestMyCodeClient::new(
1034            server.url().parse().unwrap(),
1035            "client".to_string(),
1036            "version".to_string(),
1037        )
1038        .unwrap()
1039    }
1040
1041    fn client_matcher() -> Matcher {
1042        Matcher::AllOf(vec![
1043            Matcher::UrlEncoded("client".to_string(), "client".to_string()),
1044            Matcher::UrlEncoded("client_version".to_string(), "version".to_string()),
1045        ])
1046    }
1047
1048    fn mock_get(server: &mut Server, path: &str, body: &str) -> Mock {
1049        server
1050            .mock("GET", path)
1051            .match_query(client_matcher())
1052            .with_body(body)
1053            .create()
1054    }
1055
1056    #[test]
1057    fn gets_credentials() {
1058        init();
1059        let mut server = Server::new();
1060
1061        let client = make_client(&server);
1062        let _m = mock_get(
1063            &mut server,
1064            "/api/v8/application/client/credentials",
1065            r#"
1066        {
1067            "application_id": "id",
1068            "secret": "s"
1069        }
1070        "#,
1071        );
1072
1073        let _res = get_credentials(&client).unwrap();
1074    }
1075
1076    #[test]
1077    fn gets_submission_processing_status() {
1078        init();
1079        let mut server = Server::new();
1080
1081        let client = make_client(&server);
1082        let _m = server.mock("GET", "/api/v8/core/submissions/0")
1083            .match_query(Matcher::AllOf(vec![
1084                Matcher::UrlEncoded("client".into(), "client".into()),
1085                Matcher::UrlEncoded("client_version".into(), "version".into()),
1086            ]))
1087            .with_body(serde_json::json!({
1088            "api_version": 7,
1089            "all_tests_passed": true,
1090            "user_id": 3232,
1091            "login": "014464865",
1092            "course": "mooc-java-programming-i",
1093            "exercise_name": "part01-Part01_01.Sandbox",
1094            "status": "ok",
1095            "points": [
1096                "01-01"
1097            ],
1098            "validations": {
1099                "strategy": "DISABLED",
1100                "validationErrors": {}
1101            },
1102            "valgrind": "",
1103            "submission_url": "https://tmc.mooc.fi/submissions/7402793",
1104            "solution_url": "https://tmc.mooc.fi/exercises/83113/solution",
1105            "submitted_at": "2020-06-15T16:05:08.105+03:00",
1106            "processing_time": 13,
1107            "reviewed": false,
1108            "requests_review": true,
1109            "paste_url": null,
1110            "message_for_paste": null,
1111            "missing_review_points": [],
1112            "test_cases": [
1113                {
1114                    "name": "SandboxTest freePoints",
1115                    "successful": true,
1116                    "message": "",
1117                    "exception": [],
1118                    "detailed_message": null
1119                }
1120            ],
1121            "feedback_questions": [
1122                {
1123                    "id": 389,
1124                    "question": "How well did you concentrate doing this exercise? (1: not at all, 5: very well)",
1125                    "kind": "intrange[1..5]"
1126                },
1127                {
1128                    "id": 390,
1129                    "question": "How much do you feel you learned doing this exercise? (1: Did not learn anything, 5: Learned a lot)",
1130                    "kind": "intrange[1..5]"
1131                },
1132            ],
1133            "feedback_answer_url": "https://tmc.mooc.fi/api/v8/core/submissions/7402793/feedback"
1134        }).to_string()).create();
1135
1136        let submission_processing_status = get_submission(&client, 0).unwrap();
1137        match submission_processing_status {
1138            SubmissionProcessingStatus::Finished(f) => {
1139                assert_eq!(f.all_tests_passed, Some(true));
1140            }
1141            SubmissionProcessingStatus::Processing(_) => panic!("wrong status"),
1142        }
1143    }
1144
1145    #[test]
1146    fn user_get() {
1147        init();
1148        let mut server = Server::new();
1149
1150        let client = &make_client(&server);
1151        let _m = mock_get(
1152            &mut server,
1153            "/api/v8/users/0",
1154            r#"
1155{
1156    "id": 1,
1157    "username": "student",
1158    "email": "student@example.com",
1159    "administrator": false
1160}
1161"#,
1162        );
1163        let _user = user::get(client, 0).unwrap();
1164    }
1165
1166    #[test]
1167    fn user_get_current() {
1168        init();
1169        let mut server = Server::new();
1170
1171        let client = &make_client(&server);
1172        let _m = mock_get(
1173            &mut server,
1174            "/api/v8/users/current",
1175            r#"
1176{
1177    "id": 1,
1178    "username": "student",
1179    "email": "student@example.com",
1180    "administrator": false
1181}
1182"#,
1183        );
1184
1185        let _res = user::get_current(client).unwrap();
1186    }
1187
1188    #[test]
1189    fn user_get_basic_info_by_usernames() {
1190        init();
1191        let mut server = Server::new();
1192
1193        let client = &make_client(&server);
1194        let _m = server
1195            .mock("POST", "/api/v8/users/basic_info_by_usernames")
1196            .match_query(client_matcher())
1197            .match_body(Matcher::JsonString(
1198                r#"{"usernames": ["username"]}"#.to_string(),
1199            ))
1200            .with_body(
1201                r#"
1202[
1203  {
1204    "id": 1,
1205    "username": "student",
1206    "email": "student@example.com",
1207    "administrator": false
1208  }
1209]
1210"#,
1211            )
1212            .create();
1213
1214        let _res = user::get_basic_info_by_usernames(client, &["username".to_string()]).unwrap();
1215    }
1216
1217    #[test]
1218    fn user_get_basic_info_by_emails() {
1219        init();
1220        let mut server = Server::new();
1221
1222        let client = &make_client(&server);
1223        let _m = server
1224            .mock("POST", "/api/v8/users/basic_info_by_emails")
1225            .match_query(client_matcher())
1226            .match_body(Matcher::JsonString(r#"{"emails": ["email"]}"#.to_string()))
1227            .with_body(
1228                r#"
1229[
1230  {
1231    "id": 1,
1232    "username": "student",
1233    "email": "student@example.com",
1234    "administrator": false
1235  }
1236]
1237"#,
1238            )
1239            .create();
1240
1241        let _res = user::get_basic_info_by_emails(client, &["email".to_string()]).unwrap();
1242    }
1243
1244    #[test]
1245    fn course_get_by_id() {
1246        init();
1247        let mut server = Server::new();
1248
1249        let client = &make_client(&server);
1250        let _m = mock_get(
1251            &mut server,
1252            "/api/v8/courses/0",
1253            r#"
1254{
1255    "name": "organizationid-coursename",
1256    "hide_after": "2016-10-10T13:22:19.554+03:00",
1257    "hidden": false,
1258    "cache_version": 1,
1259    "spreadsheet_key": "string",
1260    "hidden_if_registered_after": "string",
1261    "refreshed_at": "2016-10-10T13:22:36.871+03:00",
1262    "locked_exercise_points_visible": true,
1263    "description": "",
1264    "paste_visibility": 0,
1265    "formal_name": "string",
1266    "certificate_downloadable": false,
1267    "certificate_unlock_spec": "string",
1268    "organization_id": 1,
1269    "disabled_status": "enabled",
1270    "title": "testcourse",
1271    "material_url": "",
1272    "course_template_id": 1,
1273    "hide_submission_results": false,
1274    "external_scoreboard_url": "string",
1275    "organization_slug": "hy"
1276}
1277"#,
1278        );
1279
1280        let _res = course::get_by_id(client, 0).unwrap();
1281    }
1282
1283    #[test]
1284    fn course_get() {
1285        init();
1286        let mut server = Server::new();
1287
1288        let client = &make_client(&server);
1289        let _m = mock_get(
1290            &mut server,
1291            "/api/v8/org/someorg/courses/somecourse",
1292            r#"
1293{
1294    "name": "organizationid-coursename",
1295    "hide_after": "2016-10-10T13:22:19.554+03:00",
1296    "hidden": false,
1297    "cache_version": 1,
1298    "spreadsheet_key": "string",
1299    "hidden_if_registered_after": "string",
1300    "refreshed_at": "2016-10-10T13:22:36.871+03:00",
1301    "locked_exercise_points_visible": true,
1302    "description": "",
1303    "paste_visibility": 0,
1304    "formal_name": "string",
1305    "certificate_downloadable": false,
1306    "certificate_unlock_spec": "string",
1307    "organization_id": 1,
1308    "disabled_status": "enabled",
1309    "title": "testcourse",
1310    "material_url": "",
1311    "course_template_id": 1,
1312    "hide_submission_results": false,
1313    "external_scoreboard_url": "string",
1314    "organization_slug": "hy"
1315}
1316"#,
1317        );
1318
1319        let _res = course::get(client, "someorg", "somecourse").unwrap();
1320    }
1321
1322    #[test]
1323    fn point_get_course_points_by_id() {
1324        init();
1325        let mut server = Server::new();
1326
1327        let client = &make_client(&server);
1328        let _m = mock_get(
1329            &mut server,
1330            "/api/v8/courses/0/points",
1331            r#"
1332[
1333  {
1334    "awarded_point": {
1335      "id": 1,
1336      "course_id": 1,
1337      "user_id": 1,
1338      "submission_id": 2,
1339      "name": "point name",
1340      "created_at": "2016-10-17T11:10:17.295+03:00"
1341    },
1342    "exercise_id": 1
1343  }
1344]
1345"#,
1346        );
1347
1348        let _res = point::get_course_points_by_id(client, 0).unwrap();
1349    }
1350
1351    #[test]
1352    fn point_get_exercise_points_by_id() {
1353        init();
1354        let mut server = Server::new();
1355
1356        let client = &make_client(&server);
1357        let _m = mock_get(
1358            &mut server,
1359            "/api/v8/courses/0/exercises/someexercise/points",
1360            r#"
1361[
1362  {
1363    "awarded_point": {
1364      "id": 1,
1365      "course_id": 1,
1366      "user_id": 1,
1367      "submission_id": 2,
1368      "name": "point name",
1369      "created_at": "2016-10-17T11:10:17.295+03:00"
1370    },
1371    "exercise_id": 1
1372  }
1373]
1374"#,
1375        );
1376
1377        let _res = point::get_exercise_points_by_id(client, 0, "someexercise").unwrap();
1378    }
1379
1380    #[test]
1381    fn point_get_exercise_points_for_user_by_id() {
1382        init();
1383        let mut server = Server::new();
1384
1385        let client = &make_client(&server);
1386        let _m = mock_get(
1387            &mut server,
1388            "/api/v8/courses/0/exercises/someexercise/users/1/points",
1389            r#"
1390[
1391  {
1392    "awarded_point": {
1393      "id": 1,
1394      "course_id": 1,
1395      "user_id": 1,
1396      "submission_id": 2,
1397      "name": "point name",
1398      "created_at": "2016-10-17T11:10:17.295+03:00"
1399    },
1400    "exercise_id": 1
1401  }
1402]
1403"#,
1404        );
1405
1406        let _res = point::get_exercise_points_for_user_by_id(client, 0, "someexercise", 1).unwrap();
1407    }
1408
1409    #[test]
1410    fn point_get_exercise_points_for_current_user_by_id() {
1411        init();
1412        let mut server = Server::new();
1413
1414        let client = &make_client(&server);
1415        let _m = mock_get(
1416            &mut server,
1417            "/api/v8/courses/0/exercises/someexercise/users/current/points",
1418            r#"
1419[
1420  {
1421    "awarded_point": {
1422      "id": 1,
1423      "course_id": 1,
1424      "user_id": 1,
1425      "submission_id": 2,
1426      "name": "point name",
1427      "created_at": "2016-10-17T11:10:17.295+03:00"
1428    },
1429    "exercise_id": 1
1430  }
1431]
1432"#,
1433        );
1434
1435        let _res =
1436            point::get_exercise_points_for_current_user_by_id(client, 0, "someexercise").unwrap();
1437    }
1438
1439    #[test]
1440    fn point_get_course_points_for_user_by_id() {
1441        init();
1442        let mut server = Server::new();
1443
1444        let client = &make_client(&server);
1445        let _m = mock_get(
1446            &mut server,
1447            "/api/v8/courses/0/users/1/points",
1448            r#"
1449[
1450  {
1451    "awarded_point": {
1452      "id": 1,
1453      "course_id": 1,
1454      "user_id": 1,
1455      "submission_id": 2,
1456      "name": "point name",
1457      "created_at": "2016-10-17T11:10:17.295+03:00"
1458    },
1459    "exercise_id": 1
1460  }
1461]
1462"#,
1463        );
1464
1465        let _res = point::get_course_points_for_user_by_id(client, 0, 1).unwrap();
1466    }
1467
1468    #[test]
1469    fn point_get_course_points_for_current_user_by_id() {
1470        init();
1471        let mut server = Server::new();
1472
1473        let client = &make_client(&server);
1474        let _m = mock_get(
1475            &mut server,
1476            "/api/v8/courses/0/users/current/points",
1477            r#"
1478[
1479  {
1480    "awarded_point": {
1481      "id": 1,
1482      "course_id": 1,
1483      "user_id": 1,
1484      "submission_id": 2,
1485      "name": "point name",
1486      "created_at": "2016-10-17T11:10:17.295+03:00"
1487    },
1488    "exercise_id": 1
1489  }
1490]
1491"#,
1492        );
1493
1494        let _res = point::get_course_points_for_current_user_by_id(client, 0).unwrap();
1495    }
1496
1497    #[test]
1498    fn point_get_course_points() {
1499        init();
1500        let mut server = Server::new();
1501
1502        let client = &make_client(&server);
1503        let _m = mock_get(
1504            &mut server,
1505            "/api/v8/org/someorg/courses/somecourse/points",
1506            r#"
1507[
1508  {
1509    "awarded_point": {
1510      "id": 1,
1511      "course_id": 1,
1512      "user_id": 1,
1513      "submission_id": 2,
1514      "name": "point name",
1515      "created_at": "2016-10-17T11:10:17.295+03:00"
1516    },
1517    "exercise_id": 1
1518  }
1519]
1520"#,
1521        );
1522
1523        let _res = point::get_course_points(client, "someorg", "somecourse").unwrap();
1524    }
1525
1526    #[test]
1527    fn point_get_exercise_points() {
1528        init();
1529        let mut server = Server::new();
1530
1531        let client = &make_client(&server);
1532        let _m = mock_get(
1533            &mut server,
1534            "/api/v8/org/someorg/courses/somecourse/exercises/someexercise/points",
1535            r#"
1536[
1537  {
1538    "awarded_point": {
1539      "id": 1,
1540      "course_id": 1,
1541      "user_id": 1,
1542      "submission_id": 2,
1543      "name": "point name",
1544      "created_at": "2016-10-17T11:10:17.295+03:00"
1545    },
1546    "exercise_id": 1
1547  }
1548]
1549"#,
1550        );
1551
1552        let _res =
1553            point::get_exercise_points(client, "someorg", "somecourse", "someexercise").unwrap();
1554    }
1555
1556    #[test]
1557    fn point_get_course_points_for_user() {
1558        init();
1559        let mut server = Server::new();
1560
1561        let client = &make_client(&server);
1562        let _m = mock_get(
1563            &mut server,
1564            "/api/v8/org/someorg/courses/somecourse/users/0/points",
1565            r#"
1566[
1567  {
1568    "awarded_point": {
1569      "id": 1,
1570      "course_id": 1,
1571      "user_id": 1,
1572      "submission_id": 2,
1573      "name": "point name",
1574      "created_at": "2016-10-17T11:10:17.295+03:00"
1575    },
1576    "exercise_id": 1
1577  }
1578]
1579  "#,
1580        );
1581
1582        let _res = point::get_course_points_for_user(client, "someorg", "somecourse", 0).unwrap();
1583    }
1584
1585    #[test]
1586    fn point_get_course_points_for_current_user() {
1587        init();
1588        let mut server = Server::new();
1589
1590        let client = &make_client(&server);
1591        let _m = mock_get(
1592            &mut server,
1593            "/api/v8/org/someorg/courses/somecourse/users/current/points",
1594            r#"
1595[
1596  {
1597    "awarded_point": {
1598      "id": 1,
1599      "course_id": 1,
1600      "user_id": 1,
1601      "submission_id": 2,
1602      "name": "point name",
1603      "created_at": "2016-10-17T11:10:17.295+03:00"
1604    },
1605    "exercise_id": 1
1606  }
1607]
1608  "#,
1609        );
1610
1611        let _res =
1612            point::get_course_points_for_current_user(client, "someorg", "somecourse").unwrap();
1613    }
1614
1615    #[test]
1616    fn submission_get_course_submissions_by_id() {
1617        init();
1618        let mut server = Server::new();
1619
1620        let client = &make_client(&server);
1621        let _m = mock_get(
1622            &mut server,
1623            "/api/v8/courses/0/submissions",
1624            r#"
1625[
1626    {
1627      "id": 1,
1628      "user_id": 1,
1629      "pretest_error": "Missing test output. Did you terminate your program with an exit() command?",
1630      "created_at": "2016-10-17T11:10:17.295+03:00",
1631      "exercise_name": "trivial",
1632      "course_id": 1,
1633      "processed": true,
1634      "all_tests_passed": true,
1635      "points": "string",
1636      "processing_tried_at": "2016-10-17T11:10:17.295+03:00",
1637      "processing_began_at": "2016-10-17T11:10:17.295+03:00",
1638      "processing_completed_at": "2016-10-17T11:10:17.295+03:00",
1639      "times_sent_to_sandbox": 1,
1640      "processing_attempts_started_at": "2016-10-17T11:10:17.295+03:00",
1641      "params_json": "{\"error_msg_locale\":\"en\"}",
1642      "requires_review": true,
1643      "requests_review": true,
1644      "reviewed": true,
1645      "message_for_reviewer": "",
1646      "newer_submission_reviewed": true,
1647      "review_dismissed": true,
1648      "paste_available": true,
1649      "message_for_paste": "",
1650      "paste_key": "string"
1651    }
1652]
1653"#,
1654        );
1655
1656        let _res = submission::get_course_submissions_by_id(client, 0).unwrap();
1657    }
1658
1659    #[test]
1660    fn submission_get_course_submissions_for_last_hour() {
1661        init();
1662        let mut server = Server::new();
1663
1664        let client = &make_client(&server);
1665        let _m = mock_get(
1666            &mut server,
1667            "/api/v8/courses/0/submissions/last_hour",
1668            r#"
1669[
1670    1
1671]
1672"#,
1673        );
1674
1675        let _res = submission::get_course_submissions_for_last_hour(client, 0).unwrap();
1676    }
1677
1678    #[test]
1679    fn submission_get_course_submissions_for_user_by_id() {
1680        init();
1681        let mut server = Server::new();
1682
1683        let client = &make_client(&server);
1684        let _m = server.mock("GET", "/api/v8/courses/0/users/1/submissions")
1685            .match_query(client_matcher())
1686            .with_body(
1687                r#"
1688[
1689    {
1690      "id": 1,
1691      "user_id": 1,
1692      "pretest_error": "Missing test output. Did you terminate your program with an exit() command?",
1693      "created_at": "2016-10-17T11:10:17.295+03:00",
1694      "exercise_name": "trivial",
1695      "course_id": 1,
1696      "processed": true,
1697      "all_tests_passed": true,
1698      "points": "string",
1699      "processing_tried_at": "2016-10-17T11:10:17.295+03:00",
1700      "processing_began_at": "2016-10-17T11:10:17.295+03:00",
1701      "processing_completed_at": "2016-10-17T11:10:17.295+03:00",
1702      "times_sent_to_sandbox": 1,
1703      "processing_attempts_started_at": "2016-10-17T11:10:17.295+03:00",
1704      "params_json": "{\"error_msg_locale\":\"en\"}",
1705      "requires_review": true,
1706      "requests_review": true,
1707      "reviewed": true,
1708      "message_for_reviewer": "",
1709      "newer_submission_reviewed": true,
1710      "review_dismissed": true,
1711      "paste_available": true,
1712      "message_for_paste": "",
1713      "paste_key": "string"
1714    }
1715]
1716"#,
1717            )
1718            .create();
1719
1720        let _res = submission::get_course_submissions_for_user_by_id(client, 0, 1).unwrap();
1721    }
1722
1723    #[test]
1724    fn submission_get_course_submissions_for_current_user_by_id() {
1725        init();
1726        let mut server = Server::new();
1727
1728        let client = &make_client(&server);
1729        let _m = mock_get(
1730            &mut server,
1731            "/api/v8/courses/0/users/current/submissions",
1732            r#"
1733[
1734    {
1735      "id": 1,
1736      "user_id": 1,
1737      "pretest_error": "Missing test output. Did you terminate your program with an exit() command?",
1738      "created_at": "2016-10-17T11:10:17.295+03:00",
1739      "exercise_name": "trivial",
1740      "course_id": 1,
1741      "processed": true,
1742      "all_tests_passed": true,
1743      "points": "string",
1744      "processing_tried_at": "2016-10-17T11:10:17.295+03:00",
1745      "processing_began_at": "2016-10-17T11:10:17.295+03:00",
1746      "processing_completed_at": "2016-10-17T11:10:17.295+03:00",
1747      "times_sent_to_sandbox": 1,
1748      "processing_attempts_started_at": "2016-10-17T11:10:17.295+03:00",
1749      "params_json": "{\"error_msg_locale\":\"en\"}",
1750      "requires_review": true,
1751      "requests_review": true,
1752      "reviewed": true,
1753      "message_for_reviewer": "",
1754      "newer_submission_reviewed": true,
1755      "review_dismissed": true,
1756      "paste_available": true,
1757      "message_for_paste": "",
1758      "paste_key": "string"
1759    }
1760]
1761"#,
1762        );
1763
1764        let _res = submission::get_course_submissions_for_current_user_by_id(client, 0).unwrap();
1765    }
1766
1767    #[test]
1768    fn submission_get_exercise_submissions_for_user() {
1769        init();
1770        let mut server = Server::new();
1771
1772        let client = &make_client(&server);
1773        let _m = mock_get(
1774            &mut server,
1775            "/api/v8/org/someorg/courses/somecourse/users/0/submissions",
1776            r#"
1777[
1778    {
1779      "id": 1,
1780      "user_id": 1,
1781      "pretest_error": "Missing test output. Did you terminate your program with an exit() command?",
1782      "created_at": "2016-10-17T11:10:17.295+03:00",
1783      "exercise_name": "trivial",
1784      "course_id": 1,
1785      "processed": true,
1786      "all_tests_passed": true,
1787      "points": "string",
1788      "processing_tried_at": "2016-10-17T11:10:17.295+03:00",
1789      "processing_began_at": "2016-10-17T11:10:17.295+03:00",
1790      "processing_completed_at": "2016-10-17T11:10:17.295+03:00",
1791      "times_sent_to_sandbox": 1,
1792      "processing_attempts_started_at": "2016-10-17T11:10:17.295+03:00",
1793      "params_json": "{\"error_msg_locale\":\"en\"}",
1794      "requires_review": true,
1795      "requests_review": true,
1796      "reviewed": true,
1797      "message_for_reviewer": "",
1798      "newer_submission_reviewed": true,
1799      "review_dismissed": true,
1800      "paste_available": true,
1801      "message_for_paste": "",
1802      "paste_key": "string"
1803    }
1804]
1805"#,
1806        );
1807
1808        let _res = submission::get_course_submissions_for_user(client, "someorg", "somecourse", 0)
1809            .unwrap();
1810    }
1811
1812    #[test]
1813    fn submission_get_exercise_submissions_for_current_user() {
1814        init();
1815        let mut server = Server::new();
1816
1817        let client = &make_client(&server);
1818        let _m = mock_get(
1819            &mut server,
1820            "/api/v8/org/someorg/courses/somecourse/users/current/submissions",
1821            r#"
1822[
1823    {
1824      "id": 1,
1825      "user_id": 1,
1826      "pretest_error": "Missing test output. Did you terminate your program with an exit() command?",
1827      "created_at": "2016-10-17T11:10:17.295+03:00",
1828      "exercise_name": "trivial",
1829      "course_id": 1,
1830      "processed": true,
1831      "all_tests_passed": true,
1832      "points": "string",
1833      "processing_tried_at": "2016-10-17T11:10:17.295+03:00",
1834      "processing_began_at": "2016-10-17T11:10:17.295+03:00",
1835      "processing_completed_at": "2016-10-17T11:10:17.295+03:00",
1836      "times_sent_to_sandbox": 1,
1837      "processing_attempts_started_at": "2016-10-17T11:10:17.295+03:00",
1838      "params_json": "{\"error_msg_locale\":\"en\"}",
1839      "requires_review": true,
1840      "requests_review": true,
1841      "reviewed": true,
1842      "message_for_reviewer": "",
1843      "newer_submission_reviewed": true,
1844      "review_dismissed": true,
1845      "paste_available": true,
1846      "message_for_paste": "",
1847      "paste_key": "string"
1848    }
1849]
1850"#,
1851        );
1852
1853        let _res =
1854            submission::get_course_submissions_for_current_user(client, "someorg", "somecourse")
1855                .unwrap();
1856    }
1857
1858    #[test]
1859    fn submission_get_course_submissions() {
1860        init();
1861        let mut server = Server::new();
1862
1863        let client = &make_client(&server);
1864        let _m = mock_get(
1865            &mut server,
1866            "/api/v8/org/someorg/courses/somecourse/submissions",
1867            r#"
1868[
1869    {
1870      "id": 1,
1871      "user_id": 1,
1872      "pretest_error": "Missing test output. Did you terminate your program with an exit() command?",
1873      "created_at": "2016-10-17T11:10:17.295+03:00",
1874      "exercise_name": "trivial",
1875      "course_id": 1,
1876      "processed": true,
1877      "all_tests_passed": true,
1878      "points": "string",
1879      "processing_tried_at": "2016-10-17T11:10:17.295+03:00",
1880      "processing_began_at": "2016-10-17T11:10:17.295+03:00",
1881      "processing_completed_at": "2016-10-17T11:10:17.295+03:00",
1882      "times_sent_to_sandbox": 1,
1883      "processing_attempts_started_at": "2016-10-17T11:10:17.295+03:00",
1884      "params_json": "{\"error_msg_locale\":\"en\"}",
1885      "requires_review": true,
1886      "requests_review": true,
1887      "reviewed": true,
1888      "message_for_reviewer": "",
1889      "newer_submission_reviewed": true,
1890      "review_dismissed": true,
1891      "paste_available": true,
1892      "message_for_paste": "",
1893      "paste_key": "string"
1894    }
1895]
1896"#,
1897        );
1898
1899        let _res = submission::get_course_submissions(client, "someorg", "somecourse").unwrap();
1900    }
1901
1902    #[test]
1903    fn submission_get_course_submissions_for_user() {
1904        init();
1905        let mut server = Server::new();
1906
1907        let client = &make_client(&server);
1908        let _m = mock_get(
1909            &mut server,
1910            "/api/v8/org/someorg/courses/somecourse/users/0/submissions",
1911            r#"
1912[
1913    {
1914      "id": 1,
1915      "user_id": 1,
1916      "pretest_error": "Missing test output. Did you terminate your program with an exit() command?",
1917      "created_at": "2016-10-17T11:10:17.295+03:00",
1918      "exercise_name": "trivial",
1919      "course_id": 1,
1920      "processed": true,
1921      "all_tests_passed": true,
1922      "points": "string",
1923      "processing_tried_at": "2016-10-17T11:10:17.295+03:00",
1924      "processing_began_at": "2016-10-17T11:10:17.295+03:00",
1925      "processing_completed_at": "2016-10-17T11:10:17.295+03:00",
1926      "times_sent_to_sandbox": 1,
1927      "processing_attempts_started_at": "2016-10-17T11:10:17.295+03:00",
1928      "params_json": "{\"error_msg_locale\":\"en\"}",
1929      "requires_review": true,
1930      "requests_review": true,
1931      "reviewed": true,
1932      "message_for_reviewer": "",
1933      "newer_submission_reviewed": true,
1934      "review_dismissed": true,
1935      "paste_available": true,
1936      "message_for_paste": "",
1937      "paste_key": "string"
1938    }
1939]
1940"#,
1941        );
1942
1943        let _res = submission::get_course_submissions_for_user(client, "someorg", "somecourse", 0)
1944            .unwrap();
1945    }
1946
1947    #[test]
1948    fn submission_get_course_submissions_for_current_user() {
1949        init();
1950        let mut server = Server::new();
1951
1952        let client = &make_client(&server);
1953        let _m = mock_get(
1954            &mut server,
1955            "/api/v8/org/someorg/courses/somecourse/users/current/submissions",
1956            r#"
1957[
1958    {
1959      "id": 1,
1960      "user_id": 1,
1961      "pretest_error": "Missing test output. Did you terminate your program with an exit() command?",
1962      "created_at": "2016-10-17T11:10:17.295+03:00",
1963      "exercise_name": "trivial",
1964      "course_id": 1,
1965      "processed": true,
1966      "all_tests_passed": true,
1967      "points": "string",
1968      "processing_tried_at": "2016-10-17T11:10:17.295+03:00",
1969      "processing_began_at": "2016-10-17T11:10:17.295+03:00",
1970      "processing_completed_at": "2016-10-17T11:10:17.295+03:00",
1971      "times_sent_to_sandbox": 1,
1972      "processing_attempts_started_at": "2016-10-17T11:10:17.295+03:00",
1973      "params_json": "{\"error_msg_locale\":\"en\"}",
1974      "requires_review": true,
1975      "requests_review": true,
1976      "reviewed": true,
1977      "message_for_reviewer": "",
1978      "newer_submission_reviewed": true,
1979      "review_dismissed": true,
1980      "paste_available": true,
1981      "message_for_paste": "",
1982      "paste_key": "string"
1983    }
1984]
1985"#,
1986        );
1987
1988        let _res =
1989            submission::get_course_submissions_for_current_user(client, "someorg", "somecourse")
1990                .unwrap();
1991    }
1992
1993    #[test]
1994    fn exercise_get_course_exercises_by_id() {
1995        init();
1996        let mut server = Server::new();
1997
1998        let client = &make_client(&server);
1999        let _m = mock_get(
2000            &mut server,
2001            "/api/v8/courses/0/exercises",
2002            r#"
2003[
2004    {
2005      "id": 1,
2006      "name": "Exercise name",
2007      "publish_time": "2016-10-24T14:06:36.730+03:00",
2008      "solution_visible_after": "2016-10-24T14:06:36.730+03:00",
2009      "deadline": "2016-10-24T14:06:36.730+03:00",
2010      "soft_deadline": "2016-10-24T14:06:36.730+03:00",
2011      "disabled": false,
2012      "awarded_points": [],
2013      "available_points": [
2014        {
2015          "id": 1,
2016          "exercise_id": 1,
2017          "name": "Point name",
2018          "requires_review": false
2019        }
2020      ],
2021      "unlocked": false
2022    }
2023]
2024"#,
2025        );
2026
2027        let _res = exercise::get_course_exercises_by_id(client, 0).unwrap();
2028    }
2029
2030    #[test]
2031    fn exercise_get_exercise_submissions_for_user() {
2032        init();
2033        let mut server = Server::new();
2034
2035        let client = &make_client(&server);
2036        let _m = mock_get(
2037            &mut server,
2038            "/api/v8/exercises/0/users/1/submissions",
2039            r#"
2040[
2041    {
2042      "id": 1,
2043      "user_id": 1,
2044      "pretest_error": "Missing test output. Did you terminate your program with an exit() command?",
2045      "created_at": "2016-10-17T11:10:17.295+03:00",
2046      "exercise_name": "trivial",
2047      "course_id": 1,
2048      "processed": true,
2049      "all_tests_passed": true,
2050      "points": "string",
2051      "processing_tried_at": "2016-10-17T11:10:17.295+03:00",
2052      "processing_began_at": "2016-10-17T11:10:17.295+03:00",
2053      "processing_completed_at": "2016-10-17T11:10:17.295+03:00",
2054      "times_sent_to_sandbox": 1,
2055      "processing_attempts_started_at": "2016-10-17T11:10:17.295+03:00",
2056      "params_json": "{\"error_msg_locale\":\"en\"}",
2057      "requires_review": true,
2058      "requests_review": true,
2059      "reviewed": true,
2060      "message_for_reviewer": "",
2061      "newer_submission_reviewed": true,
2062      "review_dismissed": true,
2063      "paste_available": true,
2064      "message_for_paste": "",
2065      "paste_key": "string"
2066    }
2067]
2068"#,
2069        );
2070
2071        let _res = exercise::get_exercise_submissions_for_user(client, 0, 1).unwrap();
2072    }
2073
2074    #[test]
2075    fn exercise_get_exercise_submissions_for_current_user() {
2076        init();
2077        let mut server = Server::new();
2078
2079        let client = &make_client(&server);
2080        let _m = mock_get(
2081            &mut server,
2082            "/api/v8/exercises/0/users/current/submissions",
2083            r#"
2084[
2085    {
2086      "id": 1,
2087      "user_id": 1,
2088      "pretest_error": "Missing test output. Did you terminate your program with an exit() command?",
2089      "created_at": "2016-10-17T11:10:17.295+03:00",
2090      "exercise_name": "trivial",
2091      "course_id": 1,
2092      "processed": true,
2093      "all_tests_passed": true,
2094      "points": "string",
2095      "processing_tried_at": "2016-10-17T11:10:17.295+03:00",
2096      "processing_began_at": "2016-10-17T11:10:17.295+03:00",
2097      "processing_completed_at": "2016-10-17T11:10:17.295+03:00",
2098      "times_sent_to_sandbox": 1,
2099      "processing_attempts_started_at": "2016-10-17T11:10:17.295+03:00",
2100      "params_json": "{\"error_msg_locale\":\"en\"}",
2101      "requires_review": true,
2102      "requests_review": true,
2103      "reviewed": true,
2104      "message_for_reviewer": "",
2105      "newer_submission_reviewed": true,
2106      "review_dismissed": true,
2107      "paste_available": true,
2108      "message_for_paste": "",
2109      "paste_key": "string"
2110    }
2111]
2112"#,
2113        );
2114
2115        let _res = exercise::get_exercise_submissions_for_current_user(client, 0).unwrap();
2116    }
2117
2118    #[test]
2119    fn exercise_get_course_exercises() {
2120        init();
2121        let mut server = Server::new();
2122
2123        let client = &make_client(&server);
2124        let _m = mock_get(
2125            &mut server,
2126            "/api/v8/org/someorg/courses/somecourse/exercises",
2127            r#"
2128[
2129    {
2130      "id": 1,
2131      "name": "Exercise name",
2132      "publish_time": "2016-10-24T14:06:36.730+03:00",
2133      "solution_visible_after": "2016-10-24T14:06:36.730+03:00",
2134      "deadline": "2016-10-24T14:06:36.730+03:00",
2135      "soft_deadline": "2016-10-24T14:06:36.730+03:00",
2136      "disabled": false,
2137      "awarded_points": [],
2138      "available_points": [
2139        {
2140          "id": 1,
2141          "exercise_id": 1,
2142          "name": "Point name",
2143          "requires_review": false
2144        }
2145      ],
2146      "unlocked": false
2147    }
2148]
2149"#,
2150        );
2151
2152        let _res = exercise::get_course_exercises(client, "someorg", "somecourse").unwrap();
2153    }
2154
2155    #[test]
2156    fn exercise_download_course_exercise() {
2157        init();
2158        let mut server = Server::new();
2159
2160        let client = &make_client(&server);
2161        let _m = server
2162            .mock(
2163                "GET",
2164                "/api/v8/org/someorg/courses/somecourse/exercises/someexercise/download",
2165            )
2166            .match_query(client_matcher())
2167            .with_body(b"1234")
2168            .create();
2169
2170        let mut temp = tempfile::tempfile().unwrap();
2171        exercise::download_course_exercise(
2172            client,
2173            "someorg",
2174            "somecourse",
2175            "someexercise",
2176            &mut temp,
2177        )
2178        .unwrap();
2179        let mut buf = vec![];
2180        temp.rewind().unwrap();
2181        temp.read_to_end(&mut buf).unwrap();
2182        assert!(!buf.is_empty());
2183    }
2184
2185    #[test]
2186    fn organization_get_organizations() {
2187        init();
2188        let mut server = Server::new();
2189
2190        let client = &make_client(&server);
2191        let _m = mock_get(
2192            &mut server,
2193            "/api/v8/org.json",
2194            r#"
2195        [
2196  {
2197    "name": "University of Helsinki",
2198    "information": "Organization for University of Helsinki",
2199    "slug": "hy",
2200    "logo_path": "/logos/hy_logo.png",
2201    "pinned": false
2202  }
2203]
2204"#,
2205        );
2206
2207        let _res = organization::get_organizations(client).unwrap();
2208    }
2209
2210    #[test]
2211    fn organization_get_organization() {
2212        init();
2213        let mut server = Server::new();
2214
2215        let client = &make_client(&server);
2216        let _m = mock_get(
2217            &mut server,
2218            "/api/v8/org/someorg.json",
2219            r#"
2220        {
2221    "name": "University of Helsinki",
2222    "information": "Organization for University of Helsinki",
2223    "slug": "hy",
2224    "logo_path": "/logos/hy_logo.png",
2225    "pinned": false
2226}
2227"#,
2228        );
2229
2230        let _res = organization::get_organization(client, "someorg").unwrap();
2231    }
2232
2233    #[test]
2234    fn core_get_course() {
2235        init();
2236        let mut server = Server::new();
2237
2238        let client = &make_client(&server);
2239        let _m = mock_get(
2240            &mut server,
2241            "/api/v8/core/courses/0",
2242            r#"
2243{
2244  "course": {
2245      "id": 13,
2246      "name": "organizationid-coursename",
2247      "title": "coursetitle",
2248      "description": "description of the course",
2249      "details_url": "http://tmc.mooc.fi/api/v8/core/courses/13",
2250      "unlock_url": "https://tmc.mooc.fi/api/v8/core/courses/13/unlock",
2251      "reviews_url": "https://tmc.mooc.fi/api/v8/core/courses/13/reviews",
2252      "comet_url": "https://tmc.mooc.fi:8443/comet",
2253      "spyware_urls": [
2254        "http://mooc.spyware.testmycode.net/"
2255      ],
2256      "unlockables": [
2257        ""
2258      ],
2259      "exercises": [
2260        {
2261          "id": 1,
2262          "name": "Exercise name",
2263          "locked": false,
2264          "deadline_description": "2016-02-29 23:59:00 +0200",
2265          "deadline": "2016-02-29T23:59:00.000+02:00",
2266          "checksum": "f25e139769b2688e213938456959eeaf",
2267          "return_url": "https://tmc.mooc.fi/api/v8/core/exercises/1337/submissions",
2268          "zip_url": "https://tmc.mooc.fi/api/v8/core/exercises/4272/download",
2269          "returnable": true,
2270          "requires_review": false,
2271          "attempted": false,
2272          "completed": false,
2273          "reviewed": false,
2274          "all_review_points_given": true,
2275          "memory_limit": 1024,
2276          "runtime_params": [
2277            "-Xss64M"
2278          ],
2279          "valgrind_strategy": "fail",
2280          "code_review_requests_enabled": false,
2281          "run_tests_locally_action_enabled": true,
2282          "exercise_submissions_url": "https://tmc.mooc.fi/api/v8/core/exercises/1337/solution/download",
2283          "latest_submission_url": "https://tmc.mooc.fi/api/v8/core/exercises/1337",
2284          "latest_submission_id": 13337,
2285          "solution_zip_url": "http://tmc.mooc.fi/api/v8/core/submissions/1337/download"
2286        }
2287      ]
2288    }
2289}
2290    "#,
2291        );
2292
2293        let _res = core::get_course(client, 0).unwrap();
2294    }
2295
2296    #[test]
2297    fn core_get_course_reviews() {
2298        init();
2299        let mut server = Server::new();
2300
2301        let client = &make_client(&server);
2302        let _m = mock_get(
2303            &mut server,
2304            "/api/v8/core/courses/0/reviews",
2305            r#"
2306        [
2307    {
2308      "submission_id": 1,
2309      "exercise_name": "trivial",
2310      "id": 1,
2311      "marked_as_read": false,
2312      "reviewer_name": "hn",
2313      "review_body": "",
2314      "points": [
2315        "string"
2316      ],
2317      "points_not_awarded": [
2318        "string"
2319      ],
2320      "url": "http://localhost:3000/api/core/v8/submissions/1/reviews",
2321      "update_url": "http://localhost:3000/api/v8/core/courses/1/reviews/1",
2322      "created_at": "2016-10-10T13:22:19.554+03:00",
2323      "updated_at": "2016-10-10T13:22:19.554+03:00"
2324    }
2325  ]
2326  "#,
2327        );
2328
2329        let _res = core::get_course_reviews(client, 0).unwrap();
2330    }
2331
2332    #[test]
2333    fn core_update_course_review() {
2334        init();
2335        let mut server = Server::new();
2336
2337        let client = &make_client(&server);
2338        let _m = server
2339            .mock("PUT", "/api/v8/core/courses/0/reviews/1")
2340            .match_query(client_matcher())
2341            .match_body(Matcher::AllOf(vec![
2342                Matcher::UrlEncoded("review[review_body]".to_string(), "body".to_string()),
2343                Matcher::UrlEncoded("mark_as_read".to_string(), "true".to_string()),
2344            ]))
2345            .create();
2346
2347        core::update_course_review(client, 0, 1, Some("body".to_string()), Some(true)).unwrap();
2348    }
2349
2350    #[test]
2351    fn core_unlock_course() {
2352        init();
2353        let mut server = Server::new();
2354
2355        let client = &make_client(&server);
2356        let _m = server
2357            .mock("POST", "/api/v8/core/courses/0/unlock")
2358            .match_query(client_matcher())
2359            .create();
2360
2361        core::unlock_course(client, 0).unwrap();
2362    }
2363
2364    #[test]
2365    fn core_download_exercise() {
2366        init();
2367        let mut server = Server::new();
2368
2369        let client = &make_client(&server);
2370        let _m = server
2371            .mock("GET", "/api/v8/core/exercises/0/download")
2372            .match_query(client_matcher())
2373            .with_body(b"1234")
2374            .create();
2375
2376        let mut temp = tempfile::tempfile().unwrap();
2377        core::download_exercise(client, 0, &mut temp).unwrap();
2378        let mut buf = vec![];
2379        temp.rewind().unwrap();
2380        temp.read_to_end(&mut buf).unwrap();
2381        assert!(!buf.is_empty());
2382    }
2383
2384    #[test]
2385    fn core_get_exercise() {
2386        init();
2387        let mut server = Server::new();
2388
2389        let client = &make_client(&server);
2390        let _m = mock_get(
2391            &mut server,
2392            "/api/v8/core/exercises/0",
2393            r#"
2394        {
2395      "course_name": "course",
2396      "course_id": 1,
2397      "code_review_requests_enabled": true,
2398      "run_tests_locally_action_enabled": true,
2399      "exercise_name": "exercise",
2400      "exercise_id": 1,
2401      "unlocked_at": "2016-12-05T12:00:00.000+03:00",
2402      "deadline": "2016-12-24T00:00:00.000+03:00",
2403      "submissions": [
2404        {
2405          "exercise_name": "exercise",
2406          "id": 1,
2407          "user_id": 1,
2408          "course_id": 1,
2409          "created_at": "2016-12-05T12:00:00.000+03:00",
2410          "all_tests_passed": true,
2411          "points": "point1",
2412          "submitted_zip_url": "http://example.com/api/v8/core/submissions/1/download",
2413          "paste_url": "http://example.com/paste/qqbKk2Z7INqBH8cmaZ7i_A,",
2414          "processing_time": 25,
2415          "reviewed": false,
2416          "requests_review": false
2417        }
2418      ]
2419    }
2420    "#,
2421        );
2422
2423        let _res = core::get_exercise(client, 0).unwrap();
2424    }
2425
2426    #[test]
2427    fn core_get_exercise_details() {
2428        init();
2429        let mut server = Server::new();
2430
2431        let client = &make_client(&server);
2432        let _m = server
2433            .mock("GET", "/api/v8/core/exercises/details")
2434            .match_query(Matcher::AllOf(vec![
2435                client_matcher(),
2436                Matcher::UrlEncoded("ids".to_string(), "0,1".to_string()),
2437            ]))
2438            .with_body(
2439                r#"
2440{
2441  "exercises": [
2442    {
2443      "id": 1,
2444      "course_name": "course",
2445      "exercise_name": "exercise",
2446      "checksum": "f25e139769b2688e213938456959eeaf",
2447      "hide_submission_results": false
2448    }
2449  ]
2450}
2451  "#,
2452            )
2453            .create();
2454
2455        let _res = core::get_exercise_details(client, &[0, 1]).unwrap();
2456    }
2457
2458    #[test]
2459    fn core_download_exercise_solution() {
2460        init();
2461        let mut server = Server::new();
2462
2463        let client = &make_client(&server);
2464        let _m = server
2465            .mock("GET", "/api/v8/core/exercises/0/solution/download")
2466            .match_query(client_matcher())
2467            .with_body(b"1234")
2468            .create();
2469
2470        let mut temp = tempfile::tempfile().unwrap();
2471        core::download_exercise_solution(client, 0, &mut temp).unwrap();
2472        let mut buf = vec![];
2473        temp.rewind().unwrap();
2474        temp.read_to_end(&mut buf).unwrap();
2475        assert!(!buf.is_empty());
2476    }
2477
2478    #[test]
2479    fn core_submit_exercise() {
2480        init();
2481        let mut server = Server::new();
2482
2483        let client = &make_client(&server);
2484        let _m = server
2485            .mock("POST", "/api/v8/core/exercises/0/submissions")
2486            .match_query(client_matcher())
2487            .match_body(Matcher::AllOf(vec![
2488                Matcher::Regex("submission\\[file\\]".to_string()),
2489                Matcher::Regex("paste".to_string()),
2490                Matcher::Regex("message_for_paste".to_string()),
2491                Matcher::Regex("request_review".to_string()),
2492                Matcher::Regex("message_for_reviewer".to_string()),
2493                Matcher::Regex("error_msg_locale".to_string()),
2494            ]))
2495            .with_body(
2496                r#"
2497            {
2498      "show_submission_url": "someurl",
2499      "paste_url": "anotherurl",
2500      "submission_url": "third"
2501    }
2502    "#,
2503            )
2504            .create();
2505
2506        let _res = core::submit_exercise(
2507            client,
2508            0,
2509            Cursor::new(vec![]),
2510            Some(PasteData::WithMessage("paste".to_string())),
2511            Some(ReviewData::WithMessage("message".to_string())),
2512            Some(Language::from_639_1("fi").unwrap()),
2513        )
2514        .unwrap();
2515    }
2516
2517    #[test]
2518    fn core_get_organization_courses() {
2519        init();
2520        let mut server = Server::new();
2521
2522        let client = &make_client(&server);
2523        let _m = mock_get(
2524            &mut server,
2525            "/api/v8/core/org/someorg/courses",
2526            r#"
2527        [
2528  {
2529    "id": 13,
2530    "name": "organizationid-coursename",
2531    "title": "coursetitle",
2532    "description": "description of the course",
2533    "details_url": "https://tmc.mooc.fi/api/v8/core/courses/13",
2534    "unlock_url": "https://tmc.mooc.fi/api/v8/core/courses/13/unlock",
2535    "reviews_url": "https://tmc.mooc.fi/api/v8/core/courses/13/reviews",
2536    "comet_url": "https://tmc.mooc.fi:8443/comet",
2537    "spyware_urls": [
2538      "http://mooc.spyware.testmycode.net/"
2539    ]
2540  }
2541]
2542"#,
2543        );
2544
2545        let _res = core::get_organization_courses(client, "someorg").unwrap();
2546    }
2547
2548    #[test]
2549    fn core_download_submission() {
2550        init();
2551        let mut server = Server::new();
2552
2553        let client = &make_client(&server);
2554        let _m = server
2555            .mock("GET", "/api/v8/core/submissions/0/download")
2556            .match_query(client_matcher())
2557            .with_body(b"1234")
2558            .create();
2559
2560        let mut temp = tempfile::tempfile().unwrap();
2561        core::download_submission(client, 0, &mut temp).unwrap();
2562        let mut buf = vec![];
2563        temp.rewind().unwrap();
2564        temp.read_to_end(&mut buf).unwrap();
2565        assert!(!buf.is_empty());
2566    }
2567
2568    #[test]
2569    fn core_post_submission_feedback() {
2570        init();
2571        let mut server = Server::new();
2572
2573        let client = &make_client(&server);
2574        let _m = server
2575            .mock("POST", "/api/v8/core/submissions/0/feedback")
2576            .match_query(client_matcher())
2577            .match_body(Matcher::AllOf(vec![
2578                Matcher::UrlEncoded("answers[0][question_id]".to_string(), "0".to_string()),
2579                Matcher::UrlEncoded("answers[0][answer]".to_string(), "ans".to_string()),
2580            ]))
2581            .with_body(
2582                r#"
2583            {
2584                "api_version": 0,
2585                "status": "processing"
2586            }"#,
2587            )
2588            .create();
2589
2590        let _res = core::post_submission_feedback(
2591            client,
2592            0,
2593            vec![FeedbackAnswer {
2594                answer: "ans".to_string(),
2595                question_id: 0,
2596            }],
2597        )
2598        .unwrap();
2599    }
2600
2601    #[test]
2602    fn core_post_submission_review() {
2603        init();
2604        let mut server = Server::new();
2605
2606        let client = &make_client(&server);
2607        let _m = server
2608            .mock("POST", "/api/v8/core/submissions/0/reviews")
2609            .match_query(client_matcher())
2610            .match_body(Matcher::UrlEncoded(
2611                "review[review_body]".to_string(),
2612                "review".to_string(),
2613            ))
2614            .create();
2615
2616        core::post_submission_review(client, 0, "review".to_string()).unwrap();
2617    }
2618}