mitchellh-rubyuw 0.7.2 → 0.99.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -1
- data/{README.markdown → README.md} +6 -11
- data/Rakefile +12 -2
- data/VERSION +1 -1
- data/lib/rubyuw.rb +8 -0
- data/lib/rubyuw/base.rb +122 -0
- data/lib/rubyuw/connection.rb +145 -0
- data/lib/rubyuw/curriculum_enrollment.rb +75 -0
- data/lib/rubyuw/errors.rb +16 -0
- data/lib/rubyuw/sln.rb +108 -0
- data/test/live/curriculum_enrollment_test.rb +47 -0
- data/test/live/sln_test.rb +16 -26
- data/test/live/test_helper.rb +15 -2
- data/test/mocked/base_test.rb +171 -0
- data/test/mocked/connection_test.rb +178 -0
- data/test/mocked/curriculum_enrollment_test.rb +132 -61
- data/test/mocked/sln_test.rb +106 -142
- data/test/mocked/test_helper.rb +57 -1
- metadata +20 -29
- data/lib/myuw.rb +0 -67
- data/lib/myuw/curriculum_enrollment.rb +0 -110
- data/lib/myuw/errors.rb +0 -18
- data/lib/myuw/registration.rb +0 -98
- data/lib/myuw/schedule.rb +0 -74
- data/lib/myuw/session.rb +0 -91
- data/lib/myuw/sln.rb +0 -131
- data/rubyuw.gemspec +0 -102
- data/test/live/registration_test.rb +0 -46
- data/test/live/schedule_test.rb +0 -28
- data/test/live/session_test.rb +0 -34
- data/test/mocked/myuw_test.rb +0 -39
- data/test/mocked/registration_test.rb +0 -105
- data/test/mocked/schedule_test.rb +0 -132
- data/test/mocked/session_test.rb +0 -96
data/test/mocked/test_helper.rb
CHANGED
@@ -1,11 +1,67 @@
|
|
1
|
-
require
|
1
|
+
require 'rubygems'
|
2
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
|
3
|
+
require 'rubyuw'
|
2
4
|
require 'test/unit'
|
3
5
|
require "contest"
|
4
6
|
require "stories"
|
5
7
|
require "mocha"
|
6
8
|
|
7
9
|
class Test::Unit::TestCase
|
10
|
+
def setup
|
11
|
+
RubyUW::Base.reset_connection!
|
12
|
+
RubyUW::Base.connection.follow_meta_refresh = false
|
13
|
+
end
|
14
|
+
|
8
15
|
def path_to_html(html)
|
9
16
|
"file://" + File.join(File.dirname(__FILE__), 'fixture_pages', "#{html}.html")
|
10
17
|
end
|
18
|
+
|
19
|
+
def expect_get(return_page, with=nil, expectation_type = :expects)
|
20
|
+
return_html = RubyUW::Base.connection.get(path_to_html(return_page))
|
21
|
+
expectation_obj = RubyUW::Base.connection.send(expectation_type, :get).returns(return_html)
|
22
|
+
expectation_obj.with(with) unless with.nil?
|
23
|
+
|
24
|
+
return_html
|
25
|
+
end
|
26
|
+
|
27
|
+
def mock_flow(flow)
|
28
|
+
flow_seq = sequence('flow')
|
29
|
+
|
30
|
+
flow.each do |task, params, return_html|
|
31
|
+
return_page = RubyUW::Base.connection.get(path_to_html(return_html))
|
32
|
+
|
33
|
+
relay_form = return_page.form_with(:name => 'relay')
|
34
|
+
relay_form.stubs(:submit) if relay_form
|
35
|
+
|
36
|
+
RubyUW::Base.connection.expects("execute_#{task}!").with(anything, *params).returns(return_page).in_sequence(flow_seq)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Ugly hack. Avoiding using send() since that will not work in
|
42
|
+
# Ruby 1.9. Also, since I'm trying to make SINGLETON methods
|
43
|
+
# public, I can't do the Class.send(:public, *Class.protected_methods)
|
44
|
+
# trick. So this is the ugly hack until I can find a way around it.
|
45
|
+
# Anyone?
|
46
|
+
class Class
|
47
|
+
def publicize_singleton_methods
|
48
|
+
class << self
|
49
|
+
@_publicize_internals = protected_instance_methods
|
50
|
+
public *@_publicize_internals
|
51
|
+
end
|
52
|
+
|
53
|
+
if block_given?
|
54
|
+
yield
|
55
|
+
|
56
|
+
class <<self
|
57
|
+
protected *@_publicize_internals
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def protect_singleton_methods
|
63
|
+
class <<self
|
64
|
+
protected *@_publicize_internals
|
65
|
+
end
|
66
|
+
end
|
11
67
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mitchellh-rubyuw
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.99.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mitchell Hashimoto
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-09-
|
12
|
+
date: 2009-09-14 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -20,34 +20,32 @@ dependencies:
|
|
20
20
|
requirements:
|
21
21
|
- - ">="
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 0.9.
|
23
|
+
version: 0.9.3.20090623142847
|
24
24
|
version:
|
25
|
-
description:
|
25
|
+
description: Library which provides a ruby interface to the University of Washington student portal.
|
26
26
|
email: mitchell.hashimoto@gmail.com
|
27
27
|
executables: []
|
28
28
|
|
29
29
|
extensions: []
|
30
30
|
|
31
31
|
extra_rdoc_files:
|
32
|
-
- README.
|
32
|
+
- README.md
|
33
33
|
files:
|
34
34
|
- .gitignore
|
35
|
-
- README.
|
35
|
+
- README.md
|
36
36
|
- Rakefile
|
37
37
|
- VERSION
|
38
|
-
- lib/
|
39
|
-
- lib/
|
40
|
-
- lib/
|
41
|
-
- lib/
|
42
|
-
- lib/
|
43
|
-
- lib/
|
44
|
-
-
|
45
|
-
- rubyuw.gemspec
|
46
|
-
- test/live/registration_test.rb
|
47
|
-
- test/live/schedule_test.rb
|
48
|
-
- test/live/session_test.rb
|
38
|
+
- lib/rubyuw.rb
|
39
|
+
- lib/rubyuw/base.rb
|
40
|
+
- lib/rubyuw/connection.rb
|
41
|
+
- lib/rubyuw/curriculum_enrollment.rb
|
42
|
+
- lib/rubyuw/errors.rb
|
43
|
+
- lib/rubyuw/sln.rb
|
44
|
+
- test/live/curriculum_enrollment_test.rb
|
49
45
|
- test/live/sln_test.rb
|
50
46
|
- test/live/test_helper.rb
|
47
|
+
- test/mocked/base_test.rb
|
48
|
+
- test/mocked/connection_test.rb
|
51
49
|
- test/mocked/curriculum_enrollment_test.rb
|
52
50
|
- test/mocked/fixture_pages/bad_request.html
|
53
51
|
- test/mocked/fixture_pages/curric_courses.html
|
@@ -74,15 +72,12 @@ files:
|
|
74
72
|
- test/mocked/fixture_pages/sln_no_enrollment_info.html
|
75
73
|
- test/mocked/fixture_pages/sln_status.html
|
76
74
|
- test/mocked/fixture_pages/welcome.html
|
77
|
-
- test/mocked/myuw_test.rb
|
78
|
-
- test/mocked/registration_test.rb
|
79
|
-
- test/mocked/schedule_test.rb
|
80
|
-
- test/mocked/session_test.rb
|
81
75
|
- test/mocked/sln_test.rb
|
82
76
|
- test/mocked/test_helper.rb
|
83
77
|
- test/password.rb.sample
|
84
78
|
has_rdoc: false
|
85
79
|
homepage: http://github.com/mitchellh/rubyuw
|
80
|
+
licenses:
|
86
81
|
post_install_message:
|
87
82
|
rdoc_options:
|
88
83
|
- --charset=UTF-8
|
@@ -103,20 +98,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
98
|
requirements: []
|
104
99
|
|
105
100
|
rubyforge_project:
|
106
|
-
rubygems_version: 1.
|
101
|
+
rubygems_version: 1.3.5
|
107
102
|
signing_key:
|
108
103
|
specification_version: 3
|
109
104
|
summary: Library which provides a ruby interface to the University of Washington student portal.
|
110
105
|
test_files:
|
111
|
-
- test/live/
|
112
|
-
- test/live/schedule_test.rb
|
113
|
-
- test/live/session_test.rb
|
106
|
+
- test/live/curriculum_enrollment_test.rb
|
114
107
|
- test/live/sln_test.rb
|
115
108
|
- test/live/test_helper.rb
|
109
|
+
- test/mocked/base_test.rb
|
110
|
+
- test/mocked/connection_test.rb
|
116
111
|
- test/mocked/curriculum_enrollment_test.rb
|
117
|
-
- test/mocked/myuw_test.rb
|
118
|
-
- test/mocked/registration_test.rb
|
119
|
-
- test/mocked/schedule_test.rb
|
120
|
-
- test/mocked/session_test.rb
|
121
112
|
- test/mocked/sln_test.rb
|
122
113
|
- test/mocked/test_helper.rb
|
data/lib/myuw.rb
DELETED
@@ -1,67 +0,0 @@
|
|
1
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
-
|
3
|
-
require 'rubygems'
|
4
|
-
require 'mechanize'
|
5
|
-
|
6
|
-
require 'myuw/errors'
|
7
|
-
require 'myuw/session'
|
8
|
-
require 'myuw/sln'
|
9
|
-
require 'myuw/schedule'
|
10
|
-
require 'myuw/registration'
|
11
|
-
require 'myuw/curriculum_enrollment'
|
12
|
-
|
13
|
-
class MyUW
|
14
|
-
extend Forwardable
|
15
|
-
|
16
|
-
attr_accessor :browser
|
17
|
-
attr_accessor :session
|
18
|
-
attr_accessor :schedule
|
19
|
-
attr_accessor :registration
|
20
|
-
|
21
|
-
def initialize
|
22
|
-
# Initialize the browser instance and mascarade
|
23
|
-
# around as Safari on Mac
|
24
|
-
@browser = WWW::Mechanize.new do |browser|
|
25
|
-
browser.user_agent_alias = 'Mac Safari'
|
26
|
-
browser.follow_meta_refresh = true
|
27
|
-
|
28
|
-
# Workaround to avoid frozen object error SSL pages
|
29
|
-
browser.keep_alive = false
|
30
|
-
|
31
|
-
# Do not keep any history
|
32
|
-
browser.max_history = 0
|
33
|
-
end
|
34
|
-
|
35
|
-
# Initialize other members
|
36
|
-
@session = Session.new(self)
|
37
|
-
@schedule = Schedule.new(self)
|
38
|
-
@registration = Registration.new(self)
|
39
|
-
end
|
40
|
-
|
41
|
-
# Forward the session methods
|
42
|
-
def_delegator :session, :logged_in?
|
43
|
-
def_delegator :session, :logout
|
44
|
-
|
45
|
-
# Log into MyUW with a given username and password
|
46
|
-
def login(user, pass)
|
47
|
-
@session.email = user
|
48
|
-
@session.password = pass
|
49
|
-
@session.login
|
50
|
-
end
|
51
|
-
|
52
|
-
# Creates a new SLN object and returns it
|
53
|
-
def sln(sln_number, term)
|
54
|
-
sln_obj = SLNInfo.new(self)
|
55
|
-
sln_obj.sln = sln_number
|
56
|
-
sln_obj.term = term
|
57
|
-
return sln_obj
|
58
|
-
end
|
59
|
-
|
60
|
-
# Creates a new curriculum enrollment object and returns it
|
61
|
-
def curriculum_enrollment(curric, term)
|
62
|
-
curric_obj = CurriculumEnrollment.new(self)
|
63
|
-
curric_obj.curriculum = curric
|
64
|
-
curric_obj.term = term
|
65
|
-
return curric_obj
|
66
|
-
end
|
67
|
-
end
|
@@ -1,110 +0,0 @@
|
|
1
|
-
class MyUW
|
2
|
-
# = Synopsis
|
3
|
-
# Gets information regarding enrollment within a specific
|
4
|
-
# subject.
|
5
|
-
class CurriculumEnrollment
|
6
|
-
attr_reader :term
|
7
|
-
attr_reader :curriculum
|
8
|
-
attr_accessor :myuw
|
9
|
-
attr_reader :data
|
10
|
-
|
11
|
-
def initialize(myuw)
|
12
|
-
@myuw = myuw
|
13
|
-
@term = nil
|
14
|
-
@data = nil
|
15
|
-
@curriculum = nil
|
16
|
-
end
|
17
|
-
|
18
|
-
# Custom setter for SLN to reset data
|
19
|
-
def curriculum=(value)
|
20
|
-
@curriculum = value
|
21
|
-
@data = nil
|
22
|
-
end
|
23
|
-
|
24
|
-
# Custom setter for term to reset data
|
25
|
-
def term=(value)
|
26
|
-
@term = value
|
27
|
-
@data = nil
|
28
|
-
end
|
29
|
-
|
30
|
-
# Fetches the information for the given SLN.
|
31
|
-
def fetch_data
|
32
|
-
raise ArgumentError.new("Curriculum not set.") if @curriculum.nil?
|
33
|
-
raise ArgumentError.new("Term not set.") if @term.nil?
|
34
|
-
raise NotLoggedInError.new("User must be logged in to fetch SLN data") unless @myuw.logged_in?
|
35
|
-
|
36
|
-
# Request the actual page
|
37
|
-
page = get_dept_page
|
38
|
-
|
39
|
-
# Get the actual course information
|
40
|
-
end
|
41
|
-
|
42
|
-
# Get data for a specific SLN
|
43
|
-
def data_by_sln(sln, reload = false)
|
44
|
-
fetch_data if @data.nil? || reload
|
45
|
-
@data[sln]
|
46
|
-
end
|
47
|
-
|
48
|
-
# Gets the SLN info page for the SLN and term, raising
|
49
|
-
# an exception if it is requested too soon
|
50
|
-
def get_dept_page
|
51
|
-
page = @myuw.browser.get("https://sdb.admin.washington.edu/timeschd/uwnetid/tsstat.asp?QTRYR=#{@term}&CURRIC=#{@curriculum}")
|
52
|
-
if page.body.to_s =~ /No sections found for #{@curriculum}./
|
53
|
-
raise InvalidCurriculumError.new("Curriculum does not exist.")
|
54
|
-
end
|
55
|
-
|
56
|
-
page
|
57
|
-
end
|
58
|
-
|
59
|
-
# Gets and stores all course data
|
60
|
-
def get_all_courses_data(page)
|
61
|
-
nodes = page.search('//table//tr[count(th)>1 and @bgcolor="#d0d0d0"]//following-sibling::tr')
|
62
|
-
raise InvalidPageError.new("Couldn't find any courses listed.") if nodes.empty?
|
63
|
-
|
64
|
-
@data = {}
|
65
|
-
nodes.each do |node|
|
66
|
-
node_data = get_course_data(node)
|
67
|
-
@data[node_data[:sln]] = node_data
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
# Gets a single course row's data
|
72
|
-
def get_course_data(row)
|
73
|
-
nodes = row.search('td')
|
74
|
-
raise InvalidPageError.new("Couldn't find course information for a single row.") if nodes.empty?
|
75
|
-
|
76
|
-
data_keys = [:sln, :course, :section, :type, :title, :current_enrollment, :limit_enrollment,
|
77
|
-
:room_capacity, :space_available, nil, :notes]
|
78
|
-
|
79
|
-
data = extract_info(data_keys, nodes)
|
80
|
-
raise InvalidPageError.new("Failed to extract data for single row.") if !data
|
81
|
-
|
82
|
-
data
|
83
|
-
end
|
84
|
-
|
85
|
-
# Extracts course information from each node and
|
86
|
-
# stores it as the proper key in the @data variable
|
87
|
-
def extract_info(keys, nodes)
|
88
|
-
return false if nodes.empty?
|
89
|
-
|
90
|
-
data = {}
|
91
|
-
nodes.each_with_index do |node, i|
|
92
|
-
# Make sure we have keys left still
|
93
|
-
if i < keys.length then
|
94
|
-
data_key = keys[i]
|
95
|
-
|
96
|
-
# If the key is nil, we skip this node
|
97
|
-
unless data_key.nil?
|
98
|
-
data[data_key] = node.inner_text.strip.gsub("\302\240", "")
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
data
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
#
|
108
|
-
# Exceptions
|
109
|
-
class InvalidCurriculumError < StandardError; end
|
110
|
-
end
|
data/lib/myuw/errors.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
# General purpose errors used within MyUW
|
2
|
-
|
3
|
-
class MyUW
|
4
|
-
class NotLoggedInError < StandardError; end
|
5
|
-
|
6
|
-
# A generic error to capture strange invalid HTML
|
7
|
-
# results. These errors can typically be used to
|
8
|
-
# find edge cases of HTML (such as maintenance
|
9
|
-
# pages and so on) in MyUW
|
10
|
-
class InvalidPageError < StandardError
|
11
|
-
attr_reader :page
|
12
|
-
|
13
|
-
def initialize(page, msg=nil)
|
14
|
-
@page = page
|
15
|
-
super(msg)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/lib/myuw/registration.rb
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
class MyUW
|
2
|
-
# = Synopsis
|
3
|
-
# The Registration class can automate the registration
|
4
|
-
# process for a user. It does NOT support initial
|
5
|
-
# setup of the registration process such as verifying
|
6
|
-
# addresses and such (and doesnt plan to, that is
|
7
|
-
# an important step that should be done manually)
|
8
|
-
class Registration
|
9
|
-
attr_accessor :myuw
|
10
|
-
attr_reader :errors
|
11
|
-
|
12
|
-
def initialize(myuw=nil)
|
13
|
-
@myuw ||= myuw
|
14
|
-
@errors = nil
|
15
|
-
end
|
16
|
-
|
17
|
-
# Registers for one or more courses. The courses
|
18
|
-
# parameter should be an Array of strings (the
|
19
|
-
# SLNs)
|
20
|
-
def register(courses)
|
21
|
-
raise NotLoggedInError.new("User must be logged in to register for classes.") unless @myuw.logged_in?
|
22
|
-
|
23
|
-
# Get registration page
|
24
|
-
register_page = @myuw.browser.get("https://sdb.admin.washington.edu/students/uwnetid/register.asp")
|
25
|
-
register_form = register_page.form_with(:name => 'regform')
|
26
|
-
raise InvalidPageError.new(register_page, "Could not verify registration page") if register_form.nil?
|
27
|
-
|
28
|
-
# Fill in the form
|
29
|
-
fill_registration_form(register_form, courses)
|
30
|
-
|
31
|
-
# Submit it and analyze result
|
32
|
-
get_registration_result(register_form.submit())
|
33
|
-
end
|
34
|
-
|
35
|
-
def get_registration_result(result_page)
|
36
|
-
return true if result_page.body =~ /ALT="OK">Schedule updated\./i
|
37
|
-
raise InvalidPageError.new(result_page, "Unknown registration result.") unless result_page.body =~ /ALT="ERROR">Schedule not updated\./i
|
38
|
-
|
39
|
-
# Otherwise, get the errors out of the result page
|
40
|
-
extract_registration_errors(result_page)
|
41
|
-
false
|
42
|
-
end
|
43
|
-
|
44
|
-
def extract_registration_errors(result_page)
|
45
|
-
# //input[@type='hidden' and @name='action4']/following-sibling::input[@type='hidden']/following-sibling::td
|
46
|
-
registration_form = result_page.form_with(:name => 'regform')
|
47
|
-
start_index = get_add_section_index(registration_form)
|
48
|
-
current_index = start_index.to_i
|
49
|
-
|
50
|
-
@errors = {}
|
51
|
-
|
52
|
-
loop do
|
53
|
-
# Get current SLN, and if its empty, then return out
|
54
|
-
current_sln = registration_form["sln#{current_index}"]
|
55
|
-
return if current_sln.nil? || current_sln.empty?
|
56
|
-
|
57
|
-
error_cell = result_page.search("//input[@name='action#{current_index}']/parent::tr//td[5]")
|
58
|
-
raise InvalidPageError.new(result_page, "Couldn't extract errors (couldn't get data cells)") if error_cell.empty?
|
59
|
-
|
60
|
-
error_text = error_cell[0].inner_text.strip.gsub("\302\240", '')
|
61
|
-
@errors[current_sln] = error_text
|
62
|
-
|
63
|
-
current_index += 1
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def fill_registration_form(register_form, courses)
|
68
|
-
# Determine where to begin adding SLNs (what fields)
|
69
|
-
start_index = get_add_section_index(register_form)
|
70
|
-
|
71
|
-
current_index = start_index.to_i
|
72
|
-
courses.each do |course|
|
73
|
-
register_form["sln#{current_index}"] = course.to_s
|
74
|
-
current_index += 1
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
# The registration page is one giant form.
|
79
|
-
# We need to figure out which index fields
|
80
|
-
# to begin filling in with SLN data. This
|
81
|
-
# method returns the index.
|
82
|
-
def get_add_section_index(register_form)
|
83
|
-
# First way is to add maxadds and maxdrops and
|
84
|
-
# determine the offset.
|
85
|
-
maxdrops = register_form["maxdrops"].to_i
|
86
|
-
index_by_hiddens = maxdrops + 1
|
87
|
-
|
88
|
-
# Verify by inspecting actions
|
89
|
-
1.upto(11) do |i|
|
90
|
-
# Find the version action with an "A" (meaning add)
|
91
|
-
if register_form["action#{i}"].to_s == "A"
|
92
|
-
raise InvalidPageError.new(nil, "Couldn't determine add offset.") unless i == index_by_hiddens
|
93
|
-
return i.to_s
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|