diff --git a/Gemfile.lock b/Gemfile.lock index cb0e14c..987133c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,10 +2,23 @@ PATH remote: . specs: coursemology-evaluator (0.0.0) + active_rest_client (~> 1.2) + activesupport (~> 4.2.0) GEM remote: https://rubygems.org/ specs: + active_rest_client (1.2.0) + activesupport + crack + faraday + multi_json + activesupport (4.2.5) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) codeclimate-test-reporter (0.4.8) simplecov (>= 0.7.1, < 1.0.0) coveralls (0.8.10) @@ -15,14 +28,22 @@ GEM term-ansicolor (~> 1.3) thor (~> 0.19.1) tins (~> 1.6.0) + crack (0.4.3) + safe_yaml (~> 1.0.0) diff-lcs (1.2.5) docile (1.1.5) domain_name (0.5.25) unf (>= 0.0.5, < 1.0.0) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) http-cookie (1.0.2) domain_name (~> 0.5) + i18n (0.7.0) json (1.8.3) mime-types (2.99) + minitest (5.8.3) + multi_json (1.11.2) + multipart-post (2.0.0) netrc (0.11.0) rake (10.4.2) rest-client (1.8.0) @@ -42,6 +63,7 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.4.0) rspec-support (3.4.1) + safe_yaml (1.0.4) simplecov (0.11.1) docile (~> 1.1.0) json (~> 1.8) @@ -50,7 +72,10 @@ GEM term-ansicolor (1.3.2) tins (~> 1.0) thor (0.19.1) + thread_safe (0.3.5) tins (1.6.0) + tzinfo (1.2.2) + thread_safe (~> 0.1) unf (0.1.4) unf_ext unf_ext (0.0.7.1) diff --git a/bin/evaluator b/bin/evaluator new file mode 100644 index 0000000..65bf7fd --- /dev/null +++ b/bin/evaluator @@ -0,0 +1,5 @@ +#!/usr/bin/env ruby +require 'bundler/setup' +require 'coursemology/evaluator' + +Coursemology::Evaluator::CLI.start(ARGV) diff --git a/coursemology-evaluator.gemspec b/coursemology-evaluator.gemspec index c217775..115f546 100644 --- a/coursemology-evaluator.gemspec +++ b/coursemology-evaluator.gemspec @@ -24,4 +24,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'simplecov' spec.add_development_dependency 'coveralls' spec.add_development_dependency 'codeclimate-test-reporter' + + spec.add_dependency 'activesupport', '~> 4.2.0' + spec.add_dependency 'active_rest_client', '~> 1.2' end diff --git a/lib/coursemology/evaluator.rb b/lib/coursemology/evaluator.rb index 406384e..6cb6283 100644 --- a/lib/coursemology/evaluator.rb +++ b/lib/coursemology/evaluator.rb @@ -1,7 +1,11 @@ +require 'active_support/all' +require 'active_rest_client' require 'coursemology/evaluator/version' -module Coursemology - module Evaluator - # Your code goes here... - end +module Coursemology::Evaluator + extend ActiveSupport::Autoload + + autoload :Client + autoload :CLI + autoload :Models end diff --git a/lib/coursemology/evaluator/cli.rb b/lib/coursemology/evaluator/cli.rb new file mode 100644 index 0000000..30e7f46 --- /dev/null +++ b/lib/coursemology/evaluator/cli.rb @@ -0,0 +1,47 @@ +require 'optparse' + +class Coursemology::Evaluator::CLI + Options = Struct.new(:host, :api_token, :api_user_email) + + def self.start(argv) + new.start(argv) + end + + def start(argv) + run(argv) + end + + def run(argv) + options = optparse!(argv) + Coursemology::Evaluator::Client.initialize(options.host, options.api_user_email, + options.api_token) + Coursemology::Evaluator::Client.new.run + end + + private + + # Parses the options specified on the command line. + # + # @param [Array] argv The arguments specified on the command line. + # @return [Coursemology::Evaluator::CLI::Options] + def optparse!(argv) # rubocop:disable Metrics/MethodLength + options = Options.new + option_parser = OptionParser.new do |parser| + parser.banner = "Usage: #{parser.program_name} [options]" + parser.on('-hHOST', '--host=HOST', 'Coursemology host to connect to') do |host| + options.host = host + end + + parser.on('-tTOKEN', '--api-token=TOKEN') do |token| + options.api_token = token + end + + parser.on('-uUSER', '--api-user-email=USER') do |user| + options.api_user_email = user + end + end + + option_parser.parse!(argv) + options + end +end diff --git a/lib/coursemology/evaluator/client.rb b/lib/coursemology/evaluator/client.rb new file mode 100644 index 0000000..df59822 --- /dev/null +++ b/lib/coursemology/evaluator/client.rb @@ -0,0 +1,13 @@ +class Coursemology::Evaluator::Client + def self.initialize(host, api_user_email, api_token) + Coursemology::Evaluator::Models::Base.base_url = host + Coursemology::Evaluator::Models::Base.api_user_email = api_user_email + Coursemology::Evaluator::Models::Base.api_token = api_token + end + + def initialize + end + + def run + end +end diff --git a/lib/coursemology/evaluator/models.rb b/lib/coursemology/evaluator/models.rb new file mode 100644 index 0000000..80b24ce --- /dev/null +++ b/lib/coursemology/evaluator/models.rb @@ -0,0 +1,6 @@ +module Coursemology::Evaluator::Models + extend ActiveSupport::Autoload + + autoload :Base + autoload :ProgrammingEvaluation +end diff --git a/lib/coursemology/evaluator/models/base.rb b/lib/coursemology/evaluator/models/base.rb new file mode 100644 index 0000000..457c48c --- /dev/null +++ b/lib/coursemology/evaluator/models/base.rb @@ -0,0 +1,16 @@ +class Coursemology::Evaluator::Models::Base < ActiveRestClient::Base + class << self + attr_accessor :api_user_email + attr_accessor :api_token + end + + before_request :add_authentication + + private + + # Adds the authentication email and token to the request. + def add_authentication(_, request) + request.headers['X-User-Email'] = self.class.api_user_email + request.headers['X-User-Token'] = self.class.api_token + end +end diff --git a/spec/coursemology/evaluator/cli_spec.rb b/spec/coursemology/evaluator/cli_spec.rb new file mode 100644 index 0000000..bccb8d5 --- /dev/null +++ b/spec/coursemology/evaluator/cli_spec.rb @@ -0,0 +1,60 @@ +RSpec.describe Coursemology::Evaluator::CLI do + let(:host) { 'http://localhost:3000' } + let(:api_token) { 'abcd' } + let(:api_user_email) { 'test@example.org' } + let(:argv) do + ["--host=#{host}", "--api-token=#{api_token}", "--api-user-email=#{api_user_email}"] + end + + describe Coursemology::Evaluator::CLI::Options do + it { is_expected.to have_attributes(host: nil, api_token: nil, api_user_email: nil) } + end + + with_mock_client do + describe '.start' do + subject { Coursemology::Evaluator::CLI } + it 'calls #start' do + expect(subject).to receive_message_chain(:new, :start) + subject.start(argv) + end + end + + describe '#start' do + subject { Coursemology::Evaluator::CLI.new } + it 'calls #run' do + expect(subject).to receive(:run).and_call_original + subject.start(argv) + end + end + + describe '#run' do + subject { Coursemology::Evaluator::CLI.new.run(argv) } + + it 'creates a client' do + expect(Coursemology::Evaluator::Client).to receive(:new).and_call_original + subject + end + + it 'runs the client' do + expect(Coursemology::Evaluator::Client).to receive_message_chain(:new, :run) + subject + end + end + end + + describe '#optparse!' do + subject { Coursemology::Evaluator::CLI.new.send(:optparse!, argv) } + + it 'parses --host' do + expect(subject.host).to eq(host) + end + + it 'parses --api-token' do + expect(subject.api_token).to eq(api_token) + end + + it 'parses api-user-email' do + expect(subject.api_user_email).to eq(api_user_email) + end + end +end diff --git a/spec/coursemology/evaluator/client_spec.rb b/spec/coursemology/evaluator/client_spec.rb new file mode 100644 index 0000000..c28fd89 --- /dev/null +++ b/spec/coursemology/evaluator/client_spec.rb @@ -0,0 +1,18 @@ +RSpec.describe Coursemology::Evaluator::Client do + with_mock_client do + describe '.initialize' do + let(:base_url) { 'http://localhost:3000' } + let(:api_token) { 'abcd' } + let(:api_user_email) { 'test@example.org' } + + subject { Coursemology::Evaluator::Client } + + it 'sets the API parameters' do + subject.initialize(base_url, api_user_email, api_token) + expect(Coursemology::Evaluator::Models::Base.base_url).to eq(base_url) + expect(Coursemology::Evaluator::Models::Base.api_user_email).to eq(api_user_email) + expect(Coursemology::Evaluator::Models::Base.api_token).to eq(api_token) + end + end + end +end diff --git a/spec/coursemology/evaluator/models/base_spec.rb b/spec/coursemology/evaluator/models/base_spec.rb new file mode 100644 index 0000000..abb75f3 --- /dev/null +++ b/spec/coursemology/evaluator/models/base_spec.rb @@ -0,0 +1,37 @@ +RSpec.describe Coursemology::Evaluator::Models::Base do + describe 'class attributes' do + subject { Coursemology::Evaluator::Models::Base } + + describe '.api_user_email' do + it { is_expected.to have_attributes(api_user_email: nil) } + end + + describe '.api_token' do + it { is_expected.to have_attributes(api_token: nil) } + end + end + + describe '#adds_authentication' do + with_mock_client(api_user_email: 'email', api_token: 'token') do + let(:request) do + headers = {} + double.tap do |double| + expect(double).to receive(:headers).and_return(headers).at_least(:once) + end + end + subject { Coursemology::Evaluator::Models::Base.new.send(:add_authentication, nil, request) } + + it 'adds the X-User-Email header' do + subject + expect(request.headers['X-User-Email']).to \ + eq(Coursemology::Evaluator::Models::Base.api_user_email) + end + + it 'adds the X-User-Token header' do + subject + expect(request.headers['X-User-Token']).to \ + eq(Coursemology::Evaluator::Models::Base.api_token) + end + end + end +end diff --git a/spec/coursemology/evaluator_spec.rb b/spec/coursemology/evaluator_spec.rb index 4308148..4bcbc94 100644 --- a/spec/coursemology/evaluator_spec.rb +++ b/spec/coursemology/evaluator_spec.rb @@ -4,8 +4,4 @@ it 'has a version number' do expect(Coursemology::Evaluator::VERSION).not_to be nil end - - it 'does something useful' do - expect(false).to eq(true) - end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 54af732..07b9dc9 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -19,6 +19,7 @@ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) require 'coverage_helper' require 'coursemology/evaluator' +Dir["#{__dir__}/support/**/*"].each { |file| require file } RSpec.configure do |config| # rspec-expectations config goes here. You can use an alternate diff --git a/spec/support/client_stub.rb b/spec/support/client_stub.rb new file mode 100644 index 0000000..accac39 --- /dev/null +++ b/spec/support/client_stub.rb @@ -0,0 +1,50 @@ +class Coursemology::Evaluator::Client + module TestGroupHelperMethods + def self.client_options_description(host, api_user_email, api_token) + result = [] + result << "host: #{host}" if host + result << "api_user_email: #{api_user_email}" if api_user_email + result << "api_token: #{api_token}" if api_token + + result + end + + def self.mock_client(host, api_user_email, api_token) + proc do + base_model = Coursemology::Evaluator::Models::Base + instance_exec(base_model, :base_url, host, + &TestGroupHelperMethods.instance_method(:mock_property).bind(self)) + instance_exec(base_model, :api_user_email, api_user_email, + &TestGroupHelperMethods.instance_method(:mock_property).bind(self)) + instance_exec(base_model, :api_token, api_token, + &TestGroupHelperMethods.instance_method(:mock_property).bind(self)) + end + end + + def mock_property(object, property, value) + allow(object).to receive(property) do + value + end + allow(object).to receive("#{property}=".to_s) do |new_value| + value = new_value + end + end + end + + module TestGroupHelpers + def with_mock_client(host: nil, api_user_email: nil, api_token: nil, &block) + client_description = TestGroupHelperMethods. + client_options_description(host, api_user_email, api_token).to_sentence + + context "with client: #{client_description}" do + before(:each, &TestGroupHelperMethods.mock_client(host, api_user_email, api_token)) + + module_eval(&block) + end + end + end +end + +RSpec.configure do |config| + config.extend Coursemology::Evaluator::Client::TestGroupHelpers +end