From ba31d2741feb165fbacf3d1084232e4d6d91ed96 Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Sun, 10 Jan 2010 22:22:20 +0000 Subject: [PATCH] Add API to MarkUs. Add possibility to push test results and view them/download them. See review 300. Closes ticket #541 --- app/controllers/api/main_api_controller.rb | 74 +++++ .../api/test_results_controller.rb | 225 ++++++++++++++ app/controllers/main_controller.rb | 1 + app/controllers/results_controller.rb | 25 +- app/models/submission.rb | 21 +- app/models/test_result.rb | 15 + app/models/user.rb | 38 ++- app/views/api/_key_display.html.erb | 8 + app/views/assignments/grader_index.html.erb | 10 +- app/views/layouts/_menu.html.erb | 3 +- app/views/layouts/plain.html.erb | 13 + app/views/main/index.html.erb | 5 +- .../results/common/_file_selector.html.erb | 21 +- .../common/_test_result_window.html.erb | 10 + app/views/results/edit.html.erb | 7 +- app/views/results/render_test_result.html.erb | 3 + app/views/results/view_marks.html.erb | 7 +- config/environment.rb | 5 +- config/initializers/init_app.rb | 2 +- config/locales/en.yml | 6 + config/routes.rb | 8 +- ...91224042140_add_api_token_to_user_model.rb | 11 + .../20100102142037_create_test_results.rb | 20 ++ db/schema.rb | 19 +- lib/tasks/markus_api.rake | 15 + public/200.xml | 4 + public/403.xml | 3 + public/404.xml | 3 + public/422.xml | 3 + public/500.xml | 3 + public/stylesheets/main.css | 20 ++ test/fixtures/assignments.yml | 19 +- test/fixtures/groupings.yml | 4 + test/fixtures/groups.yml | 2 + test/fixtures/submissions.yml | 7 + test/fixtures/test_results.yml | 21 ++ test/fixtures/users.yml | 11 + .../api/main_api_controller_test.rb | 123 ++++++++ .../api/test_results_controller_test.rb | 287 ++++++++++++++++++ test/unit/helpers/api/main_helper_test.rb | 4 + .../helpers/api/test_results_helper_test.rb | 4 + test/unit/submission_test.rb | 18 ++ test/unit/test_result_test.rb | 62 ++++ 43 files changed, 1147 insertions(+), 23 deletions(-) create mode 100644 app/controllers/api/main_api_controller.rb create mode 100644 app/controllers/api/test_results_controller.rb create mode 100644 app/models/test_result.rb create mode 100644 app/views/api/_key_display.html.erb create mode 100644 app/views/layouts/plain.html.erb create mode 100644 app/views/results/common/_test_result_window.html.erb create mode 100755 app/views/results/render_test_result.html.erb create mode 100644 db/migrate/20091224042140_add_api_token_to_user_model.rb create mode 100644 db/migrate/20100102142037_create_test_results.rb create mode 100644 lib/tasks/markus_api.rake create mode 100644 public/200.xml create mode 100644 public/403.xml create mode 100644 public/404.xml create mode 100644 public/422.xml create mode 100644 public/500.xml create mode 100644 test/fixtures/test_results.yml create mode 100644 test/functional/api/main_api_controller_test.rb create mode 100644 test/functional/api/test_results_controller_test.rb create mode 100644 test/unit/helpers/api/main_helper_test.rb create mode 100644 test/unit/helpers/api/test_results_helper_test.rb create mode 100644 test/unit/test_result_test.rb diff --git a/app/controllers/api/main_api_controller.rb b/app/controllers/api/main_api_controller.rb new file mode 100644 index 0000000000..69e3ed0c17 --- /dev/null +++ b/app/controllers/api/main_api_controller.rb @@ -0,0 +1,74 @@ +require 'base64' + +#=== Description +# Scripting API handlers for MarkUs +module Api + + #===Description + # This is the parent class of all API controllers. + # Shared functionality of all API controllers + # should go here. + class MainApiController < ActionController::Base + + before_filter :authenticate + + #=== Description + # Dummy action (for authentication testing) + # No public route matches this action. + def index + render :file => "#{RAILS_ROOT}/public/200.xml", :status => 200 + end + + private + #=== Description + # Auth handler for the MarkUs API. It uses + # the Authorization HTTP header to determine + # the user who issued the request. With the Authorization + # HTTP header comes a Base 64 encoded MD5 digest of the + # user's private key. + def authenticate + auth_token = parse_auth_token(request.headers["HTTP_AUTHORIZATION"]) + # pretend resource not found if missing or wrong authentication + # is provided + if auth_token.nil? + render :file => "#{RAILS_ROOT}/public/403.xml", :status => 403 + return + end + # Find user by api_key_md5 + @current_user = User.find_by_api_key_md5(auth_token) + if @current_user.nil? + # Key does not exist, so bail out + render :file => "#{RAILS_ROOT}/public/403.xml", :status => 403 + return + elsif @current_user.student? + # API is available for TAs and Admins only + render :file => "#{RAILS_ROOT}/public/403.xml", :status => 403 + return + end + end + + + #=== Description + # Helper method for parsing the authentication token + def parse_auth_token(token) + return nil if token.nil? + if !(token =~ /MarkUsAuth ([^\s,]+)/).nil? + ret_token = $1 + begin + ret_token = Base64.decode64(ret_token).strip + # we expect a MD5 sum string of length 32 + return nil unless ret_token.length == 32 + # now we are good, so it seems to be a valid + # token + return ret_token + rescue Exception + return nil + end + else + return nil + end + end + + end + +end # end Api module diff --git a/app/controllers/api/test_results_controller.rb b/app/controllers/api/test_results_controller.rb new file mode 100644 index 0000000000..91ce58ca8a --- /dev/null +++ b/app/controllers/api/test_results_controller.rb @@ -0,0 +1,225 @@ +module Api + + #=== Description + # Allows for pushing of test results into MarkUs (e.g. from automated test runs). + # Uses Rails' RESTful routes (check 'rake routes' for the configured routes) + class TestResultsController < MainApiController + + #=== Description + # Triggered by a HTTP POST request to /api/test_results(.:format). + # Creates a new TestResult instance. Requires the following parameters: + # group_name: Name of the group to which the test result should be associated to + # assignment: Short identifier of the assignment + # filename: Filename of the test result + # file_content: Content of the test results + #=== Returns + # An XML response, indicating the success/failure for the request + def create + if !request.post? + # pretend this URL does not exist + render :file => "#{RAILS_ROOT}/public/404.html", :status => 404 + return + end + if !has_required_http_params_including_file_content?(params) + # incomplete/invalid HTTP params + render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422 + return + end + # check if there's a valid submission + submission = Submission.get_submission_by_group_and_assignment(params[:group_name], + params[:assignment]) + if submission.nil? + # no such submission + render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422 + return + end + # Request seems good. Check if filename already exists. + # If it does, update it instead of creating a new one. + new_test_result = submission.test_results.find_by_filename(params[:filename]) + if new_test_result.nil? + if TestResult.create(:filename => params[:filename], + :file_content => params[:file_content], + :submission_id => submission.id) + # All good, so return a success response + render :file => "#{RAILS_ROOT}/public/200.xml", :status => 200 + return + else + # Some other error occurred + render :file => "#{RAILS_ROOT}/public/500.xml", :status => 500 + return + end + else + new_test_result.file_content = params[:file_content] + if new_test_result.save + # All good, so return a success response + render :file => "#{RAILS_ROOT}/public/200.xml", :status => 200 + return + else + # Some other error occurred + render :file => "#{RAILS_ROOT}/public/500.xml", :status => 500 + return + end + end + end + + #=== Description + # Triggered by a HTTP DELETE request to /api/test_results(.:format). + # Deletes a TestResult instance. Requires the following parameters: + # group_name: Name of the group to which the test result should be associated to + # assignment: Short identifier of the assignment + # filename: Filename of the test result to be deleted + #=== Returns + # An XML response, indicating the success/failure for the request + def destroy + if !request.delete? + # pretend this URL does not exist + render :file => "#{RAILS_ROOT}/public/404.html", :status => 404 + return + end + if !has_required_http_params?(params) + # incomplete/invalid HTTP params + render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422 + return + end + # check if there's a valid submission + submission = Submission.get_submission_by_group_and_assignment(params[:group_name], + params[:assignment]) + if submission.nil? + # no such submission + render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422 + return + end + # request seems good + test_result = submission.test_results.find_by_filename(params[:filename]) + if !test_result.nil? + if test_result.destroy + # Everything went fine; report success + render :file => "#{RAILS_ROOT}/public/200.xml", :status => 200 + return + else + # Some other error occurred + render :file => "#{RAILS_ROOT}/public/500.xml", :status => 500 + return + end + end + # The test result in question does not exist + render :file => "#{RAILS_ROOT}/public/404.xml", :status => 404 + return + end + + #=== Description + # Triggered by a HTTP PUT request to /api/test_results(.:format). + # Updates (overwrites) a TestResult instance. Requires the following parameters: + # group_name: Name of the group to which the test result should be associated to + # assignment: Short identifier of the assignment + # filename: Filename of the test result, which content should be updated + # file_content: New content of the test result + #=== Returns + # An XML response, indicating the success/failure for the request + def update + if !request.put? + # pretend this URL does not exist + render :file => "#{RAILS_ROOT}/public/404.html", :status => 404 + return + end + if !has_required_http_params_including_file_content?(params) + # incomplete/invalid HTTP params + render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422 + return + end + # check if there's a valid submission + submission = Submission.get_submission_by_group_and_assignment(params[:group_name], + params[:assignment]) + if submission.nil? + # no such submission + render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422 + return + end + # request seems good + test_result = submission.test_results.find_by_filename(params[:filename]) + if !test_result.nil? + if test_result.update_file_content(params[:file_content]) + # Everything went fine; report success + render :file => "#{RAILS_ROOT}/public/200.xml", :status => 200 + return + else + # Some other error occurred + render :file => "#{RAILS_ROOT}/public/500.xml", :status => 500 + return + end + end + # The test result in question does not exist + render :file => "#{RAILS_ROOT}/public/404.xml", :status => 404 + return + end + + #=== Description + # Triggered by a HTTP GET request to /api/test_results(.:format). + # Shows a TestResult instance. Requires the following parameters: + # group_name: Name of the group to which the test result should be associated to + # assignment: Short identifier of the assignment + # filename: New filename of the test result + #=== Returns + # The content of the test result file in question + def show + if !request.get? + # pretend this URL does not exist + render :file => "#{RAILS_ROOT}/public/404.html", :status => 404 + return + end + if !has_required_http_params?(params) + # incomplete/invalid HTTP params + render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422 + return + end + # check if there's a valid submission + submission = Submission.get_submission_by_group_and_assignment(params[:group_name], + params[:assignment]) + if submission.nil? + # no such submission + render :file => "#{RAILS_ROOT}/public/422.xml", :status => 422 + return + end + # request seems good + test_result = submission.test_results.find_by_filename(params[:filename]) + if !test_result.nil? + # Everything went fine; send file_content + send_data test_result.file_content, :disposition => 'inline', + :filename => test_result.filename + return + end + # The test result in question does not exist + render :file => "#{RAILS_ROOT}/public/404.xml", :status => 404 + return + end + + private + + # Helper method to check for required HTTP parameters + def has_required_http_params?(param_hash) + # Note: The blank? method is a Rails extension. + # Specific keys have to be present, and their values + # must not be blank. + if !param_hash[:filename].blank? && + !param_hash[:assignment].blank? && + !param_hash[:group_name].blank? + return true + else + return false + end + end + + # Helper method to check for required HTTP parameters including the + # file_content parameter + def has_required_http_params_including_file_content?(param_hash) + if has_required_http_params?(param_hash) + if !param_hash[:file_content].blank? + return true + end + end + return false + end + + end # end TestResultsController + +end diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index 8db12136b5..c522566c1c 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -73,6 +73,7 @@ def login uri = session[:redirect_uri] session[:redirect_uri] = nil refresh_timeout + current_user.set_api_key # set api key in DB for user if not yet set # redirect to last visited page or to main page redirect_to( uri || { :action => 'index' } ) else diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb index 115d0da1b3..fff97643d4 100644 --- a/app/controllers/results_controller.rb +++ b/app/controllers/results_controller.rb @@ -2,12 +2,12 @@ class ResultsController < ApplicationController before_filter :authorize_only_for_admin, :except => [:codeviewer, :edit, :update_mark, :view_marks, :create, :add_extra_mark, :next_grouping, :update_overall_comment, :expand_criteria, :collapse_criteria, :remove_extra_mark, :expand_unmarked_criteria, :update_marking_state, - :download, :note_message] + :download, :note_message, :render_test_result] before_filter :authorize_for_ta_and_admin, :only => [:edit, :update_mark, :create, :add_extra_mark, :download, :next_grouping, :update_overall_comment, :expand_criteria, :collapse_criteria, :remove_extra_mark, :expand_unmarked_criteria, :update_marking_state, :note_message] - before_filter :authorize_for_user, :only => [:codeviewer] + before_filter :authorize_for_user, :only => [:codeviewer, :render_test_result] before_filter :authorize_for_student, :only => [:view_marks] def note_message @@ -29,6 +29,7 @@ def edit @grouping = @result.submission.grouping @group = @grouping.group @files = @submission.submission_files + @test_result_files = @submission.test_results @first_file = @files.first @extra_marks_points = @result.extra_marks.points @extra_marks_percentage = @result.extra_marks.percentage @@ -161,6 +162,25 @@ def codeviewer @code_type = @file.get_file_type render :action => 'results/common/codeviewer' end + + #=== Description + # Action called via Rails' remote_function from the test_result_window partial + # Prepares test result and updates content in window. + def render_test_result + @assignment = Assignment.find(params[:id]) + @test_result = TestResult.find(params[:test_result_id]) + + # Students can use this action only, when marks have been released + if current_user.student? && + (@test_result.submission.grouping.membership_status(current_user).nil? || + @test_result.submission.result.released_to_students == false) + render :partial => 'shared/handle_error', + :locals => {:error => I18n.t('test_result.error.no_access', :test_result_id => @test_result.id)} + return + end + + render :action => 'results/render_test_result', :layout => "plain" + end def update_mark result_mark = Mark.find(params[:mark_id]) @@ -212,6 +232,7 @@ def view_marks @annotation_categories = @assignment.annotation_categories @group = @grouping.group @files = @submission.submission_files + @test_result_files = @submission.test_results @first_file = @files.first @extra_marks_points = @result.extra_marks.points @extra_marks_percentage = @result.extra_marks.percentage diff --git a/app/models/submission.rb b/app/models/submission.rb index b2c1f5727f..66754122b6 100644 --- a/app/models/submission.rb +++ b/app/models/submission.rb @@ -9,8 +9,9 @@ class Submission < ActiveRecord::Base validates_numericality_of :submission_version, :only_integer => true belongs_to :grouping has_one :result, :dependent => :destroy - has_many :submission_files, :dependent => :destroy - has_many :annotations, :through => :submission_files + has_many :submission_files, :dependent => :destroy + has_many :annotations, :through => :submission_files + has_many :test_results, :dependent => :destroy def self.create_by_timestamp(grouping, timestamp) if !timestamp.kind_of? Time @@ -128,6 +129,22 @@ def populate_with_submission_files(revision, path="/") new_file.save end end + + #=== Description + # Helper class method to find a submission by providing a group_name and + # and an assignment short identifier. + #=== Returns + # nil if no such submission exists. + def self.get_submission_by_group_and_assignment(group_n, ass_si) + assignment = Assignment.find_by_short_identifier(ass_si) + group = Group.find_by_group_name(group_n) + if !assignment.nil? && !group.nil? + grouping = group.grouping_for_assignment(assignment.id) + return grouping.get_submission_used + else + return nil + end + end private diff --git a/app/models/test_result.rb b/app/models/test_result.rb new file mode 100644 index 0000000000..197435f3be --- /dev/null +++ b/app/models/test_result.rb @@ -0,0 +1,15 @@ +class TestResult < ActiveRecord::Base + belongs_to :submission + validates_presence_of :submission # we require an associated submission + validates_associated :submission # submission need to be valid + + #=== Description + # Updates the file_content attribute of an TestResult object + #=== Returns + # True if saving with the new content succeeds, false otherwise + def update_file_content(new_content) + return false if new_content.nil? + self.file_content = new_content + return self.save + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 1be830b6fe..3eb1a7686e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,4 +1,5 @@ require 'fastercsv' +require 'digest' # required for set_api_token # We always assume the following fields exists: # => :user_name, :last_name, :first_name @@ -152,7 +153,7 @@ def self.upload_user_list(user_class, user_list) return result end - def self.add_user(user_class, row) + def self.add_user(user_class, row) # convert each line to a hash with FIELDS as corresponding keys # and create or update a user with the hash values #return nil if values.length < UPLOAD_FIELDS.length @@ -174,5 +175,40 @@ def self.add_user(user_class, row) return current_user end + + # Set API key for user model. The key is a + # SHA2 512 bit long digest. The MD5 digest converted + # to a string is used for lookup and transfer token + # over the wire. + def set_api_key + if self.api_key.nil? + key = generate_api_key + self.api_key = key + md5 = Digest::MD5.new + md5.update(key) + self.api_key_md5 = md5.to_s + return self.save + else + return true + end + end + + # Resets the api key. Usually triggered, if the + # old md5 hash has gotten into the wrong hands. + def reset_api_key + key = generate_api_key + self.api_key = key + md5 = Digest::MD5.new + md5.update(key) + self.api_key_md5 = md5.to_s + return self.save + end + + private + def generate_api_key + digest = Digest::SHA2.new(bitlen=512) + # generate a unique token + return digest.update(Time.now.to_s).to_s + end end diff --git a/app/views/api/_key_display.html.erb b/app/views/api/_key_display.html.erb new file mode 100644 index 0000000000..edcf80bb5d --- /dev/null +++ b/app/views/api/_key_display.html.erb @@ -0,0 +1,8 @@ +

Your API Key

+
+ <% if !user.api_key.nil? %> + <%= user.api_key %> + <% else %> + Unavailable + <% end %> +
diff --git a/app/views/assignments/grader_index.html.erb b/app/views/assignments/grader_index.html.erb index 99cd5f183f..5e1db1f0c8 100644 --- a/app/views/assignments/grader_index.html.erb +++ b/app/views/assignments/grader_index.html.erb @@ -1,8 +1,8 @@

Grade Assignments

- -<%= render :partial => "assignments/list_manage", - :locals => { :controller => 'submissions', :action => 'browse' } -%> + <%= render :partial => "assignments/list_manage", + :locals => { :controller => 'submissions', :action => 'browse' } -%> +
+ <%= render :partial => "api/key_display", :locals => { :user => @current_user } %> +
- - diff --git a/app/views/layouts/_menu.html.erb b/app/views/layouts/_menu.html.erb index cb9a13d39b..303b4190c9 100755 --- a/app/views/layouts/_menu.html.erb +++ b/app/views/layouts/_menu.html.erb @@ -1,4 +1,5 @@ -<%# MENU %> +<% # MENU +%>
-

<%= h(@current_user.first_name) %>, welcome to <%= t(:markus) %>, the online marking tool.

+

<%= h(@current_user.first_name) %>, welcome to <%= t(:markus) %>, the online marking tool.

+
+ <%= render :partial => "api/key_display", :locals => { :user => @current_user } %> +
diff --git a/app/views/results/common/_file_selector.html.erb b/app/views/results/common/_file_selector.html.erb index 80403d8c3f..5d8c8eae95 100644 --- a/app/views/results/common/_file_selector.html.erb +++ b/app/views/results/common/_file_selector.html.erb @@ -2,7 +2,7 @@ # file when using this partial %>
-<%= form_tag({:action => "download"}) if can_download %> +<%= form_tag({:action => "download"}, {:id => "grader_view_file_selector_form"}) if can_download %> Submission File: + <% if test_result_files.empty? %> + + <% end %> + <% test_result_files.each do |file| %> + + <% end %> + + " /> + +
diff --git a/app/views/results/common/_test_result_window.html.erb b/app/views/results/common/_test_result_window.html.erb new file mode 100644 index 0000000000..30c8a26a6b --- /dev/null +++ b/app/views/results/common/_test_result_window.html.erb @@ -0,0 +1,10 @@ + diff --git a/app/views/results/edit.html.erb b/app/views/results/edit.html.erb index 22bf979279..ee0cf3f4c0 100644 --- a/app/views/results/edit.html.erb +++ b/app/views/results/edit.html.erb @@ -74,6 +74,11 @@ function check_working() { +<% +# Javascript for test result window +%> +<%= render :partial => "results/common/test_result_window.html" %> + <%= render :partial => "results/common/annotations.js" %> <%= render :partial => "results/marker/setup_annotation_categories.js" %> @@ -104,7 +109,7 @@ function check_working() { <%= render :partial => 'results/common/errors' %> -<%= render :partial => 'results/common/file_selector', :locals => {:files => @files, :can_download => true} %> +<%= render :partial => 'results/common/file_selector', :locals => {:files => @files, :can_download => true, :test_result_files => @test_result_files} %> <% if !@result.released_to_students %> diff --git a/app/views/results/render_test_result.html.erb b/app/views/results/render_test_result.html.erb new file mode 100755 index 0000000000..817288cf0b --- /dev/null +++ b/app/views/results/render_test_result.html.erb @@ -0,0 +1,3 @@ +
+  <%= h(@test_result.file_content) %>
+
diff --git a/app/views/results/view_marks.html.erb b/app/views/results/view_marks.html.erb index a16aeb34cd..37844183e2 100644 --- a/app/views/results/view_marks.html.erb +++ b/app/views/results/view_marks.html.erb @@ -42,6 +42,11 @@ <% # BOOT SCRIPTS %> <%= javascript_include_tag "Grader/tabs_boot.js" %> +<% +# Javascript for test result window +%> +<%= render :partial => "results/common/test_result_window.html" %> + <%= render :partial => "results/common/annotations.js" %> <%= render :partial => "results/student/boot.js", :locals => {:first_file => @first_file} %> @@ -51,7 +56,7 @@
<%= render :partial => 'results/common/errors' %> -<%= render :partial => 'results/common/file_selector', :locals => {:files => @files, :can_download => false} %> +<%= render :partial => 'results/common/file_selector', :locals => {:files => @files, :can_download => false, :test_result_files => @test_result_files } %> <%= render :partial => 'results/student/student_panes', :locals => {:extra_marks_points => @extra_marks_points, :extra_marks_percentage => @extra_marks_percentage, :result => @result, :rubric_criteria => @rubric_criteria, :marks_map => @marks_map, :assignment => @assignment } %>
diff --git a/config/environment.rb b/config/environment.rb index f303369da9..3db84950ac 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -42,6 +42,7 @@ # which shouldn't be used to store highly confidential information # (create the session table with "rake db:sessions:create") config.action_controller.session_store = :active_record_store - - + + # We need the api controllers loaded for all environments + config.load_paths += ["app/controllers/api"] end diff --git a/config/initializers/init_app.rb b/config/initializers/init_app.rb index e0d9f41e52..f155ab2ab7 100644 --- a/config/initializers/init_app.rb +++ b/config/initializers/init_app.rb @@ -3,4 +3,4 @@ # Add module methods to Object class. # This makes markus_config_* methods available in the classes -include MarkusConfigurator \ No newline at end of file +include MarkusConfigurator diff --git a/config/locales/en.yml b/config/locales/en.yml index 3d27616d2e..d61b47f500 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -238,6 +238,12 @@ en: error: no_access: "No access to submission file with id #{{submission_file_id}}." binary_file_message: "Binary content: Use download button to view file!" + test_result: + error: + no_access: "No access to test result with id #{{test_result_id}}." + button: + load: "Load" + no_files_available: "[No test results available]" criteria_csv_error: incomplete_row: "contains too little information." diff --git a/config/routes.rb b/config/routes.rb index da685e25c1..86266cf3dc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -40,7 +40,13 @@ map.connect 'main', :controller => 'main', :action => 'index' map.connect 'main/about', :controller => 'main', :action => 'about' map.connect 'main/logout', :controller => 'main', :action => 'logout' + + # API routes + map.namespace :api do |api| + api.resource :test_results + end + #map.connect 'api/test_results/:action/:id', :controller => 'test_results', :namespace => 'api' - # generic connects + # Generic connects map.connect 'main/:controller/:action/:id' end diff --git a/db/migrate/20091224042140_add_api_token_to_user_model.rb b/db/migrate/20091224042140_add_api_token_to_user_model.rb new file mode 100644 index 0000000000..52c9c1ec5a --- /dev/null +++ b/db/migrate/20091224042140_add_api_token_to_user_model.rb @@ -0,0 +1,11 @@ +class AddApiTokenToUserModel < ActiveRecord::Migration + def self.up + add_column :users, :api_key, :string + add_column :users, :api_key_md5, :string + end + + def self.down + remove_column :users, :api_key + remove_column :users, :api_key_md5 + end +end diff --git a/db/migrate/20100102142037_create_test_results.rb b/db/migrate/20100102142037_create_test_results.rb new file mode 100644 index 0000000000..c873f7e664 --- /dev/null +++ b/db/migrate/20100102142037_create_test_results.rb @@ -0,0 +1,20 @@ +class CreateTestResults < ActiveRecord::Migration + def self.up + create_table :test_results do |t| + t.string :filename + t.text :file_content + t.integer :submission_id + t.timestamps + end + + add_index :test_results, [:filename] + add_index :test_results, [:submission_id] + end + + def self.down + remove_index :test_results, [:filename] + remove_index :test_results, [:submission_id] + + drop_table :test_results + end +end diff --git a/db/schema.rb b/db/schema.rb index 43cd2a543b..bc55a391de 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -9,7 +9,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20091127212046) do +ActiveRecord::Schema.define(:version => 20100102142037) do create_table "annotation_categories", :force => true do |t| t.text "annotation_category_name" @@ -133,7 +133,7 @@ t.datetime "updated_at" end - add_index "grade_entry_students", ["user_id", "grade_entry_form_id"], :name => "index_grade_entry_students_on_user_id_and_grade_entry_form_id", :unique => true + add_index "grade_entry_students", ["grade_entry_form_id", "user_id"], :name => "index_grade_entry_students_on_user_id_and_grade_entry_form_id", :unique => true create_table "grades", :force => true do |t| t.integer "grade_entry_item_id" @@ -171,7 +171,7 @@ t.string "markable_type" end - add_index "marks", ["markable_id", "result_id", "markable_type"], :name => "marks_u1", :unique => true + add_index "marks", ["markable_id", "markable_type", "result_id"], :name => "marks_u1", :unique => true create_table "memberships", :force => true do |t| t.integer "user_id" @@ -277,6 +277,17 @@ add_index "submissions", ["grouping_id"], :name => "index_submissions_on_grouping_id" + create_table "test_results", :force => true do |t| + t.string "filename" + t.text "file_content" + t.integer "submission_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "test_results", ["filename"], :name => "index_test_results_on_filename" + add_index "test_results", ["submission_id"], :name => "index_test_results_on_submission_id" + create_table "users", :force => true do |t| t.string "user_name", :null => false t.string "last_name" @@ -286,6 +297,8 @@ t.datetime "created_at" t.datetime "updated_at" t.boolean "hidden", :default => false, :null => false + t.string "api_key" + t.string "api_key_md5" end add_index "users", ["user_name"], :name => "index_users_on_user_name", :unique => true diff --git a/lib/tasks/markus_api.rake b/lib/tasks/markus_api.rake new file mode 100644 index 0000000000..10e77cf3bc --- /dev/null +++ b/lib/tasks/markus_api.rake @@ -0,0 +1,15 @@ +namespace :markus do + + desc "Resets the API key for Admins and TAs" + task(:reset_api_keys => :environment) do + print("Resetting API keys for Admins and TAs...") + users = Ta.all + Admin.all + users.each do |user| + if !user.api_key.nil? + user.reset_api_key + end + end + puts(" done!") + end + +end diff --git a/public/200.xml b/public/200.xml new file mode 100644 index 0000000000..e0265ba6bb --- /dev/null +++ b/public/200.xml @@ -0,0 +1,4 @@ + + + Success + diff --git a/public/403.xml b/public/403.xml new file mode 100644 index 0000000000..629db26a0f --- /dev/null +++ b/public/403.xml @@ -0,0 +1,3 @@ + + + diff --git a/public/404.xml b/public/404.xml new file mode 100644 index 0000000000..95469eefe0 --- /dev/null +++ b/public/404.xml @@ -0,0 +1,3 @@ + + + diff --git a/public/422.xml b/public/422.xml new file mode 100644 index 0000000000..af0f25c557 --- /dev/null +++ b/public/422.xml @@ -0,0 +1,3 @@ + + + diff --git a/public/500.xml b/public/500.xml new file mode 100644 index 0000000000..407ca2c80b --- /dev/null +++ b/public/500.xml @@ -0,0 +1,3 @@ + + + diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css index 0adfd1b689..88ecec0c2e 100644 --- a/public/stylesheets/main.css +++ b/public/stylesheets/main.css @@ -1571,3 +1571,23 @@ fieldset.disabled{ display: none; } +#api_key_box { + padding: 10px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border: 1px solid #ccc; + overflow: auto; +} + +div#test_results_controls { + display: inline; + margin-left: 30px; +} + +form#grader_view_file_selector_form { + display: inline; +} + +pre#test_result_content { + font-size: 1.3em; +} diff --git a/test/fixtures/assignments.yml b/test/fixtures/assignments.yml index 1c70d6ee47..a57fb6531d 100644 --- a/test/fixtures/assignments.yml +++ b/test/fixtures/assignments.yml @@ -188,4 +188,21 @@ flexible_assignment_2: updated_at: <%= Time.now.to_s(:db)%> repository_folder: Flex2 marking_scheme_type: "flexible" - allow_web_submits: true \ No newline at end of file + allow_web_submits: true + +assignment_test_result1: + short_identifier: test_result_ass + description: "I'm here to test the test_results controller" + message: "" + due_date: <%= 2.days.from_now.to_s(:db)%> + group_min: 2 + group_max: 3 + student_form_groups: true + instructor_form_groups: false + group_name_autogenerated: false + group_name_displayed: false + created_at: <%= 10.days.ago.to_s(:db)%> + updated_at: <%= Time.now.to_s(:db)%> + repository_folder: test_results1 + marking_scheme_type: "rubric" + allow_web_submits: true diff --git a/test/fixtures/groupings.yml b/test/fixtures/groupings.yml index e85b52fb37..391d307528 100644 --- a/test/fixtures/groupings.yml +++ b/test/fixtures/groupings.yml @@ -49,4 +49,8 @@ grouping_flexible_1: grouping_flexible_2: group_id: <%= Fixtures.identify(:group_6) %> assignment_id: <%= Fixtures.identify(:flexible_assignment_2) %> + +grouping_test_result1: + group_id: <%= Fixtures.identify(:group_test_result1) %> + assignment_id: <%= Fixtures.identify(:assignment_test_result1) %> diff --git a/test/fixtures/groups.yml b/test/fixtures/groups.yml index 2ba2fb0c53..342c740ea0 100644 --- a/test/fixtures/groups.yml +++ b/test/fixtures/groups.yml @@ -37,3 +37,5 @@ group2: group3: group_name: student3 +group_test_result1: + group_name: test_result_group diff --git a/test/fixtures/submissions.yml b/test/fixtures/submissions.yml index aa54245b0f..35c4681100 100644 --- a/test/fixtures/submissions.yml +++ b/test/fixtures/submissions.yml @@ -66,3 +66,10 @@ submission_flexible_2: revision_number: 1 revision_timestamp: <%= Time.now.to_s(:db) %> +test_result_submission1: + grouping_id: <%= Fixtures.identify(:grouping_test_result1) %> + created_at: <%= Time.now.to_s(:db) %> + submission_version: 1 + submission_version_used: true + revision_number: 1 + revision_timestamp: <%= Time.now.to_s(:db) %> diff --git a/test/fixtures/test_results.yml b/test/fixtures/test_results.yml new file mode 100644 index 0000000000..aad6b23bff --- /dev/null +++ b/test/fixtures/test_results.yml @@ -0,0 +1,21 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +# one: +# column: value +# +# two: +# column: value +test_result_example: + filename: "test.txt" + file_content: "some text and some ...... :-)" + submission_id: <%= Fixtures.identify(:submission_1) %> + +test_result_controller_test1: + filename: "example.rb" + file_content: "This is an example test result.\n" + submission_id: <%= Fixtures.identify(:test_result_submission1)%> + +test_result_controller_test2: + filename: "example_script.sh" + file_content: "This is the second example test result.\n" + submission_id: <%= Fixtures.identify(:test_result_submission1)%> diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml index 639d4ef574..9ee449b39f 100644 --- a/test/fixtures/users.yml +++ b/test/fixtures/users.yml @@ -41,6 +41,17 @@ olm_admin_3: updated_at: <%= Time.now.to_s(:db) %> hidden: false +api_admin: + user_name: api_admin + last_name: admin_for_api + first_name: some + type: Admin + created_at: <%= Time.now.to_s(:db) %> + updated_at: <%= Time.now.to_s(:db) %> + hidden: false + api_key: "ccbd25868a9e63ba8fa54627d03df5df0f4b0961037360f2488c5bc1bddab839e0f1cbb0858f46e75473551844910272bd7933cda809fb12262421fef6be3c19" + api_key_md5: "8d62cd05fd10dd82e6bc286b61aa7428" + # Now some TAs... ta1: diff --git a/test/functional/api/main_api_controller_test.rb b/test/functional/api/main_api_controller_test.rb new file mode 100644 index 0000000000..1b99424d94 --- /dev/null +++ b/test/functional/api/main_api_controller_test.rb @@ -0,0 +1,123 @@ +require File.join(File.dirname(__FILE__),'../../test_helper') +require 'shoulda' +require 'base64' + +# Tests the authentication mechanism of the MarkUs API +class Api::MainApiControllerTest < ActionController::TestCase + + fixtures :users + + context "An unauthenticated GET request on any API controller" do + setup do + @res = get("index") + end + + should_respond_with :forbidden + + should "receive a 403 response" do + assert_not_nil(@res =~ //) + end + end + + context "An unauthenticated PUT request on any API controller" do + setup do + @res = put("index") + end + + should_respond_with :forbidden + + should "receive a 403 response" do + assert_not_nil(@res =~ //) + end + end + + context "An unauthenticated DELETE request on any API controller" do + setup do + @res = delete("index") + end + + should_respond_with :forbidden + + should "receive a 403 response" do + assert_not_nil(@res =~ //) + end + end + + context "An unauthenticated POST request on any API controller" do + setup do + @res = post("index") + end + + should_respond_with :forbidden + + should "receive a 403 response" do + assert_not_nil(@res =~ //) + end + end + + context "An authenticated GET request to any API controller" do + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + @res = get("index") + end + + should_assign_to :current_user + should_respond_with :success + should "render a success response" do + res_file = File.new("#{RAILS_ROOT}/public/200.xml") + assert_equal(@res.body, res_file.read) + end + end + + context "An authenticated PUT request to any API controller" do + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + @res = put("index") + end + + should_assign_to :current_user + should "render a success response" do + res_file = File.new("#{RAILS_ROOT}/public/200.xml") + assert_equal(@res.body, res_file.read) + end + end + + context "An authenticated DELETE request to any API controller" do + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + @res = delete("index") + end + + should_assign_to :current_user + should "render a success response" do + res_file = File.new("#{RAILS_ROOT}/public/200.xml") + assert_equal(@res.body, res_file.read) + end + end + + context "An authenticated POST request to any API controller" do + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + @res = post("index") + end + + should_assign_to :current_user + should "render a success response" do + res_file = File.new("#{RAILS_ROOT}/public/200.xml") + assert_equal(@res.body, res_file.read) + end + end + +end diff --git a/test/functional/api/test_results_controller_test.rb b/test/functional/api/test_results_controller_test.rb new file mode 100644 index 0000000000..da419b7ae7 --- /dev/null +++ b/test/functional/api/test_results_controller_test.rb @@ -0,0 +1,287 @@ +require File.join(File.dirname(__FILE__),'../../test_helper') +require 'shoulda' +require 'base64' + +# Tests the test results handlers (create, destroy, update, show) +class Api::TestResultsControllerTest < ActionController::TestCase + + context "An authenticated GET request to api/test_results" do + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + # get parameters from fixtures + group = groups(:group_test_result1) + assignment = assignments(:assignment_test_result1) + @test_result = test_results(:test_result_controller_test1) + group_name = group.group_name + a_short_identifier = assignment.short_identifier + filename = @test_result.filename + # fire off request + @res = get("show", {:group_name => group_name, :assignment => a_short_identifier, + :filename => filename}) + end + + should_assign_to :current_user + should "send the file contents in question" do + assert_equal(@test_result.file_content, @res.body) + end + end + + context "An authenticated POST request to api/test_results" do + + setup do + @filename = "new_test_result.txt" + @file_content = "test content\tsome more\n\rtest\n" + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + # get parameters from fixtures + group = groups(:group_test_result1) + assignment = assignments(:assignment_test_result1) + group_name = group.group_name + a_short_identifier = assignment.short_identifier + grouping = group.grouping_for_assignment(assignment.id) + @submission = grouping.get_submission_used + test_results = @submission.test_results # returns Array + @test_results_count_pre_post = test_results.length + # fire off request + @res = post("create", {:group_name => group_name, :assignment => a_short_identifier, + :filename => @filename, :file_content => @file_content}) + end + + should_assign_to :current_user + should_respond_with :success + should "create a new test result and return a 200 (success) response" do + # need to reload submission first + @submission.reload + test_results = @submission.test_results # returns Array + # check if test result object has been created + assert_equal(@test_results_count_pre_post + 1, test_results.length, + "Should have created one more test result object") + new_test_result = TestResult.find_by_filename(@filename) + assert_not_nil(new_test_result) + assert_equal(@file_content, new_test_result.file_content) + # check if a proper response has been sent + res_file = File.new("#{RAILS_ROOT}/public/200.xml") + assert_equal(res_file.read, @res.body) + end + end + + context "An authenticated DELETE request to api/test_results" do + + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + # get parameters from fixtures + group = groups(:group_test_result1) + assignment = assignments(:assignment_test_result1) + group_name = group.group_name + a_short_identifier = assignment.short_identifier + grouping = group.grouping_for_assignment(assignment.id) + @submission = grouping.get_submission_used + test_results = @submission.test_results # returns Array + @test_results_count_pre_post = test_results.length + @to_be_deleted_test_result = "example.rb" + @res = delete("destroy", {:group_name => group_name, :assignment => a_short_identifier, + :filename => @to_be_deleted_test_result}) + end + + should_assign_to :current_user + should_respond_with :success + should "delete the test result in question and return a 200 (success) response" do + # need to reload the submission object first + @submission.reload + test_results = @submission.test_results # returns Array + # check if test result object has been deleted + assert_equal(@test_results_count_pre_post - 1, test_results.length, + "Should have deleted test result object") + deleted_test_result = TestResult.find_by_filename(@to_be_deleted_test_result) + assert_nil(deleted_test_result) + # check if a proper response has been sent + res_file = File.new("#{RAILS_ROOT}/public/200.xml") + assert_equal(res_file.read, @res.body) + end + end + + context "An authenticated PUT request to api/test_results" do + + setup do + @filename = "example.rb" + @file_content = "new content\n\n" + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + # get parameters from fixtures + group = groups(:group_test_result1) + assignment = assignments(:assignment_test_result1) + group_name = group.group_name + a_short_identifier = assignment.short_identifier + grouping = group.grouping_for_assignment(assignment.id) + @res = put("update", {:group_name => group_name, :assignment => a_short_identifier, + :filename => @filename, :file_content => @file_content}) + end + + should_assign_to :current_user + should_respond_with :success + should "update the test result in question and return a 200 (success) response" do + updated_test_result = TestResult.find_by_filename(@filename) + assert_not_nil(updated_test_result) + assert_equal(@file_content, updated_test_result.file_content) + # check if a proper response has been sent + res_file = File.new("#{RAILS_ROOT}/public/200.xml") + assert_equal(res_file.read, @res.body) + end + end + + context "An authenticated GET request to api/test_results with incomplete parameters" do + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + # parameters + @res = get("show", {:filename => "some_filename"}) + end + + should_assign_to :current_user + should_respond_with 422 + should "return a 422 (Unprocessable Entity) response" do + # check if a proper response has been sent + res_file = File.new("#{RAILS_ROOT}/public/422.xml") + assert_equal(res_file.read, @res.body) + end + end + + context "An authenticated POST request to api/test_results with incomplete parameters" do + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + @res = post("create", {:filename => "some_filename"}) + end + + should_assign_to :current_user + should_respond_with 422 + should "return a 422 (Unprocessable Entity) response" do + # check if a proper response has been sent + res_file = File.new("#{RAILS_ROOT}/public/422.xml") + assert_equal(res_file.read, @res.body) + end + end + + context "An authenticated PUT request to api/test_results with incomplete parameters" do + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + @res = put("update", {:filename => "some_filename"}) + end + + should_assign_to :current_user + should_respond_with 422 + should "return a 422 (Unprocessable Entity) response" do + # check if a proper response has been sent + res_file = File.new("#{RAILS_ROOT}/public/422.xml") + assert_equal(res_file.read, @res.body) + end + end + + context "An authenticated DELETE request to api/test_results with incomplete parameters" do + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + @res = delete("destroy", {:filename => "somefilename"}) + end + + should_assign_to :current_user + should_respond_with 422 + should "return a 422 (Unprocessable Entity) response" do + # check if a proper response has been sent + res_file = File.new("#{RAILS_ROOT}/public/422.xml") + assert_equal(res_file.read, @res.body) + end + end + + context "An authenticated DELETE request to api/test_results with a non-existing filename as parameter" do + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + # get parameters from fixtures + group = groups(:group_test_result1) + assignment = assignments(:assignment_test_result1) + group_name = group.group_name + a_short_identifier = assignment.short_identifier + @res = delete("destroy", {:group_name => group_name, :assignment => a_short_identifier, + :filename => "does_not_exist"}) + end + + should_assign_to :current_user + should_respond_with 404 + should "return a 404 (Not Found) response" do + # check if a proper response has been sent + res_file = File.new("#{RAILS_ROOT}/public/404.xml") + assert_equal(res_file.read, @res.body) + end + end + + context "An authenticated GET request to api/test_results with a non-existing filename as parameter" do + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + # get parameters from fixtures + group = groups(:group_test_result1) + assignment = assignments(:assignment_test_result1) + group_name = group.group_name + a_short_identifier = assignment.short_identifier + @res = get("show", {:group_name => group_name, :assignment => a_short_identifier, + :filename => "does_not_exist"}) + end + + should_assign_to :current_user + should_respond_with 404 + should "return a 404 (Not Found) response" do + # check if a proper response has been sent + res_file = File.new("#{RAILS_ROOT}/public/404.xml") + assert_equal(res_file.read, @res.body) + end + end + + context "An authenticated PUT request to api/test_results with a non-existing filename as parameter" do + setup do + admin = users(:api_admin) + base_encoded_md5 = Base64.encode64(admin.api_key_md5).strip + auth_http_header = "MarkUsAuth #{base_encoded_md5}" + @request.env['HTTP_AUTHORIZATION'] = auth_http_header + # get parameters from fixtures + group = groups(:group_test_result1) + assignment = assignments(:assignment_test_result1) + group_name = group.group_name + a_short_identifier = assignment.short_identifier + @res = put("update", {:group_name => group_name, :assignment => a_short_identifier, + :filename => "does_not_exist", :file_content => "irrelevant"}) + end + + should_assign_to :current_user + should_respond_with 404 + should "return a 404 (Not Found) response" do + # check if a proper response has been sent + res_file = File.new("#{RAILS_ROOT}/public/404.xml") + assert_equal(res_file.read, @res.body) + end + end + +end diff --git a/test/unit/helpers/api/main_helper_test.rb b/test/unit/helpers/api/main_helper_test.rb new file mode 100644 index 0000000000..5e76f4f62b --- /dev/null +++ b/test/unit/helpers/api/main_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class Api::MainHelperTest < ActionView::TestCase +end diff --git a/test/unit/helpers/api/test_results_helper_test.rb b/test/unit/helpers/api/test_results_helper_test.rb new file mode 100644 index 0000000000..f9af2b2fc3 --- /dev/null +++ b/test/unit/helpers/api/test_results_helper_test.rb @@ -0,0 +1,4 @@ +require 'test_helper' + +class Api::TestResultsHelperTest < ActionView::TestCase +end diff --git a/test/unit/submission_test.rb b/test/unit/submission_test.rb index 841d3e9f83..d784760a1f 100644 --- a/test/unit/submission_test.rb +++ b/test/unit/submission_test.rb @@ -2,6 +2,8 @@ require 'shoulda' class SubmissionTest < ActiveSupport::TestCase + should_have_many :submission_files + should "automatically create a result" do s = submissions(:submission_1) s.save @@ -9,4 +11,20 @@ class SubmissionTest < ActiveSupport::TestCase assert_equal s.result.marking_state, Result::MARKING_STATES[:unmarked], "Result marking_state should have been automatically set to unmarked" end + context "The Submission class" do + should "be able to find a submission object by group name and assignment short identifier" do + # existing submission + submission = submissions(:test_result_submission1) + assignment = assignments(:assignment_test_result1) + group = groups(:group_test_result1) + sub2 = Submission.get_submission_by_group_and_assignment(group.group_name, + assignment.short_identifier) + assert_not_nil(sub2) + assert_instance_of(Submission, sub2) + assert_equal(submission, sub2) + # non existing test results + assert_nil(Submission.get_submission_by_group_and_assignment("group_name_not_there", "A_not_there")) + end + end + end diff --git a/test/unit/test_result_test.rb b/test/unit/test_result_test.rb new file mode 100644 index 0000000000..479a42cadf --- /dev/null +++ b/test/unit/test_result_test.rb @@ -0,0 +1,62 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'shoulda' + +class TestResultTest < ActiveSupport::TestCase + + fixtures :submissions, :test_results + + # Basic testing: create, delete, update + + context "MarkUs" do + should "be able to create and save a TestResult instance" do + sub = submissions(:submission_1) + test_r = TestResult.new + test_r.filename = "this is my filename.txt" + test_r.file_content = "Some test content." + assert(!test_r.valid?, "No submission associated, should be invalid!") + test_r.submission = sub + assert(test_r.valid?, "Submission associated, TestResult instance should be valid, now!") + assert(test_r.save, "Since Submission and TestResult is valid, it should save!") + end + + should "be able to delete a TestResult instance" do + test_res = test_results(:test_result_example) + assert(test_res.valid?, "Test result instance should be valid!") + assert(test_res.destroy, "should be able to delete a TestResult instance") + end + + should "be able to update a TestResult instance" do + FILENAME = "some value with_some text.txt" + FILE_CONTENT = "a aba asdkalfdjl adklajf dadflkaj fafjla fda\nalkdafl a\n print\t\nslfjd \n" + test_res = test_results(:test_result_example) + # pre-update sanity checks + assert_equal(test_res.file_content, "some text and some ...... :-)") + assert_equal(test_res.filename, "test.txt") + # update some values + test_res.filename = FILENAME + test_res.file_content = FILE_CONTENT + assert_equal(test_res.file_content, FILE_CONTENT, "File content should be updated") + assert_equal(test_res.filename, FILENAME, "Filename should be updated") + assert(test_res.save) + # check again after saving + assert_equal(test_res.file_content, FILE_CONTENT, "File content should be updated") + assert_equal(test_res.filename, FILENAME, "Filename should be updated") + sub2 = submissions(:submission_2) + test_res.submission = sub2 + assert(test_res.save) + assert_equal(test_res.submission, sub2, "Should be the same submission instance!") + assert(test_res.valid?, "TestResult should be valid!") + end + end + + context "A TestResult object" do + should "be able to update the file_content attribute" do + test_result = test_results(:test_result_example) + new_content = "this is the new content\t\n" + assert(test_result.update_file_content(new_content), "Should have saved successfully") + assert_equal(new_content, test_result.file_content) + # invalid content + assert(!test_result.update_file_content(nil)) + end + end +end