mitchellh-rubyuw 0.4.0 → 0.4.1
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 +2 -1
- data/Rakefile +24 -0
- data/VERSION +1 -1
- data/lib/myuw.rb +1 -5
- data/lib/myuw/session.rb +14 -10
- data/lib/myuw/sln.rb +47 -27
- data/rubyuw.gemspec +29 -3
- data/test/live/session_test.rb +34 -0
- data/test/live/sln_test.rb +64 -0
- data/test/live/test_helper.rb +5 -0
- data/test/mocked/fixture_pages/bad_request.html +80 -0
- data/test/mocked/fixture_pages/logged_in_relay.html +373 -0
- data/test/mocked/fixture_pages/login.html +121 -0
- data/test/mocked/fixture_pages/no_form_login.html +122 -0
- data/test/mocked/fixture_pages/no_login_button.html +100 -0
- data/test/mocked/fixture_pages/not_logged_in_relay.html +15 -0
- data/test/mocked/fixture_pages/sln_does_not_exist.html +85 -0
- data/test/mocked/fixture_pages/sln_status.html +92 -0
- data/test/mocked/fixture_pages/welcome.html +93 -0
- data/test/mocked/myuw_test.rb +27 -0
- data/test/mocked/session_test.rb +96 -0
- data/test/mocked/sln_test.rb +48 -0
- data/test/mocked/test_helper.rb +5 -0
- data/test/password.rb.sample +8 -0
- metadata +27 -4
data/.gitignore
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
pkg/*
|
1
|
+
pkg/*
|
2
|
+
test/password.rb
|
data/Rakefile
CHANGED
@@ -13,3 +13,27 @@ begin
|
|
13
13
|
rescue LoadError
|
14
14
|
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
15
15
|
end
|
16
|
+
|
17
|
+
def run_tests(files)
|
18
|
+
require File.join(File.dirname(__FILE__), 'lib', 'myuw')
|
19
|
+
require 'test/unit'
|
20
|
+
require "contest"
|
21
|
+
require "stories"
|
22
|
+
require "mocha"
|
23
|
+
files.each { |f| require f }
|
24
|
+
end
|
25
|
+
|
26
|
+
namespace :test do
|
27
|
+
desc "Run all the tests"
|
28
|
+
task :run => [:run_mock, :run_live]
|
29
|
+
|
30
|
+
desc "Run the library test on fixtured data"
|
31
|
+
task :run_mock do
|
32
|
+
run_tests(Dir[File.join(File.dirname(__FILE__), 'test', 'mocked', '**', '*.rb')])
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Run the library tests on live data (requires password.rb file). NOTE: This method is very slow."
|
36
|
+
task :run_live do
|
37
|
+
run_tests(Dir[File.join(File.dirname(__FILE__), 'test', 'live', '**', '*.rb')])
|
38
|
+
end
|
39
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.4.
|
1
|
+
0.4.1
|
data/lib/myuw.rb
CHANGED
data/lib/myuw/session.rb
CHANGED
@@ -18,16 +18,9 @@ class MyUW
|
|
18
18
|
# verifies by going to the MyUW homepage and seeing if the
|
19
19
|
# dashboard loads.
|
20
20
|
def logged_in?
|
21
|
-
home =
|
22
|
-
|
23
|
-
# Click the login button to hopefully get to the main MyUW
|
24
|
-
# page
|
25
|
-
entry_button = home.search("//input[@type='submit' and @value='Log in with your UW NetID']")
|
26
|
-
raise("Failed to find the log in button") if entry_button.empty?
|
27
|
-
|
21
|
+
home = get_bare_login_page
|
28
22
|
relay_page = home.form_with(:name => 'f').submit()
|
29
|
-
|
30
|
-
return !welcome_msg.empty?
|
23
|
+
!relay_page.search("span.greetingw").empty?
|
31
24
|
end
|
32
25
|
|
33
26
|
# Logs a user in using the given email and password
|
@@ -66,19 +59,30 @@ class MyUW
|
|
66
59
|
|
67
60
|
private
|
68
61
|
|
69
|
-
|
62
|
+
# Gets the myuw.washington.edu homepage. It raises an error
|
63
|
+
# if it cannot find the login button (meaning something
|
64
|
+
# weird is going on)
|
65
|
+
def get_bare_login_page
|
70
66
|
home = @myuw.browser.get("http://myuw.washington.edu")
|
71
67
|
|
72
68
|
# Click the login button to get to the forum
|
73
69
|
entry_button = home.search("//input[@type='submit' and @value='Log in with your UW NetID']")
|
74
70
|
raise("Failed to find the log in button") if entry_button.empty?
|
75
71
|
|
72
|
+
home
|
73
|
+
end
|
74
|
+
|
75
|
+
# Gets the actual login page by pressing the "Log In"
|
76
|
+
# button and following the relay
|
77
|
+
def get_login_page
|
78
|
+
home = get_bare_login_page
|
76
79
|
entry_form = home.form_with(:name => 'f')
|
77
80
|
relay_page = entry_form.submit()
|
78
81
|
|
79
82
|
return follow_relay_page(relay_page)
|
80
83
|
end
|
81
84
|
|
85
|
+
# Follows a relay form
|
82
86
|
def follow_relay_page(relay_page)
|
83
87
|
relay_form = relay_page.form_with(:name => 'relay')
|
84
88
|
relay_form.submit()
|
data/lib/myuw/sln.rb
CHANGED
@@ -30,16 +30,48 @@ class MyUW
|
|
30
30
|
# Fetches the information for the given SLN.
|
31
31
|
def fetch_data
|
32
32
|
raise("SLN not set.") if @sln.nil?
|
33
|
+
raise("Term not set.") if @term.nil?
|
33
34
|
raise("User must be logged in to fetch SLN data") unless @myuw.logged_in?
|
34
35
|
|
36
|
+
# Request the actual page
|
37
|
+
page = get_sln_page
|
38
|
+
|
39
|
+
# Get the actual course information
|
40
|
+
get_course_info page
|
41
|
+
get_enrollment_info page
|
42
|
+
get_course_notes page
|
43
|
+
end
|
44
|
+
|
45
|
+
# The methods to extract various info out of the SLN. While
|
46
|
+
# I tend to try to avoid metaprogramming, this case seemed
|
47
|
+
# simple enough. Nothing tricky happening here!
|
48
|
+
[:course, :section, :type, :credits, :title, :current_enrollment,
|
49
|
+
:limit_enrollment, :room_capacity, :space_available, :status,
|
50
|
+
:notes].each do |info|
|
51
|
+
eval(<<-eomethod)
|
52
|
+
def #{info}
|
53
|
+
fetch_data if @data.nil?
|
54
|
+
@data[:#{info}]
|
55
|
+
end
|
56
|
+
eomethod
|
57
|
+
end
|
58
|
+
|
59
|
+
# Gets the SLN info page for the SLN and term, raising
|
60
|
+
# an exception if it is requested too soon
|
61
|
+
def get_sln_page
|
35
62
|
page = @myuw.browser.get("https://sdb.admin.washington.edu/timeschd/uwnetid/sln.asp?QTRYR=#{@term}&SLN=#{@sln}")
|
36
|
-
if page.uri == "http://www.washington.edu/students/timeschd/badrequest.html"
|
63
|
+
if page.uri.to_s == "http://www.washington.edu/students/timeschd/badrequest.html"
|
37
64
|
raise("Attempted to fetch SLN data too soon.")
|
65
|
+
elsif page.body.to_s =~ /SLN: #{@sln} does not exist./
|
66
|
+
raise("SLN does not exist.")
|
38
67
|
end
|
39
68
|
|
40
|
-
|
41
|
-
|
42
|
-
|
69
|
+
page
|
70
|
+
end
|
71
|
+
|
72
|
+
# Gets the basic SLN info (the top table in the
|
73
|
+
# current status page)
|
74
|
+
def get_course_info(page)
|
43
75
|
info_nodes = page.search("//table[@border=1 and @cellpadding=3]//tr[@rowspan=1]//td")
|
44
76
|
|
45
77
|
data_order = [nil, :course, :section, :type, :credits, :title]
|
@@ -49,14 +81,15 @@ class MyUW
|
|
49
81
|
|
50
82
|
unless data_key.nil?
|
51
83
|
@data ||= {}
|
52
|
-
@data[data_key] = node.inner_text.strip
|
84
|
+
@data[data_key] = node.inner_text.strip.gsub("\302\240", "")
|
53
85
|
end
|
54
86
|
end
|
55
87
|
end
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
88
|
+
end
|
89
|
+
|
90
|
+
# Gets the enrollment information for an SLN such
|
91
|
+
# as current enrollment, space available, etc.
|
92
|
+
def get_enrollment_info(page)
|
60
93
|
info_nodes = page.search("//table[@border=1 and @cellpadding=3]//tr[count(td)=5]//td")
|
61
94
|
|
62
95
|
data_order = [:current_enrollment, :limit_enrollment, :room_capacity, :space_available, :status]
|
@@ -66,30 +99,17 @@ class MyUW
|
|
66
99
|
|
67
100
|
unless data_key.nil?
|
68
101
|
@data ||= {}
|
69
|
-
@data[data_key] = node.inner_text.strip
|
102
|
+
@data[data_key] = node.inner_text.strip.gsub("\302\240", "")
|
70
103
|
end
|
71
104
|
end
|
72
105
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
106
|
+
end
|
107
|
+
|
108
|
+
# Gets the notes for a course
|
109
|
+
def get_course_notes(page)
|
76
110
|
info_nodes = page.search("//table[@border=1 and @cellpadding=3]//tr[count(td)=1]//preceding-sibling::tr[count(th)=1]//following-sibling::tr//td")
|
77
111
|
@data ||= {}
|
78
112
|
@data[:notes] = info_nodes[0].inner_text.strip
|
79
113
|
end
|
80
|
-
|
81
|
-
# The methods to extract various info out of the SLN. While
|
82
|
-
# I tend to try to avoid metaprogramming, this case seemed
|
83
|
-
# simple enough. Nothing tricky happening here!
|
84
|
-
[:course, :section, :type, :credits, :title, :current_enrollment,
|
85
|
-
:limit_enrollment, :room_capacity, :space_available, :status,
|
86
|
-
:notes].each do |info|
|
87
|
-
eval(<<-eomethod)
|
88
|
-
def #{info}
|
89
|
-
fetch_data if @data.nil?
|
90
|
-
@data[:#{info}]
|
91
|
-
end
|
92
|
-
eomethod
|
93
|
-
end
|
94
114
|
end
|
95
115
|
end
|
data/rubyuw.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{rubyuw}
|
5
|
-
s.version = "0.4.
|
5
|
+
s.version = "0.4.1"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Mitchell Hashimoto"]
|
9
|
-
s.date = %q{2009-06-
|
9
|
+
s.date = %q{2009-06-27}
|
10
10
|
s.description = %q{TODO}
|
11
11
|
s.email = %q{mitchell.hashimoto@gmail.com}
|
12
12
|
s.extra_rdoc_files = [
|
@@ -20,7 +20,24 @@ Gem::Specification.new do |s|
|
|
20
20
|
"lib/myuw.rb",
|
21
21
|
"lib/myuw/session.rb",
|
22
22
|
"lib/myuw/sln.rb",
|
23
|
-
"rubyuw.gemspec"
|
23
|
+
"rubyuw.gemspec",
|
24
|
+
"test/live/session_test.rb",
|
25
|
+
"test/live/sln_test.rb",
|
26
|
+
"test/live/test_helper.rb",
|
27
|
+
"test/mocked/fixture_pages/bad_request.html",
|
28
|
+
"test/mocked/fixture_pages/logged_in_relay.html",
|
29
|
+
"test/mocked/fixture_pages/login.html",
|
30
|
+
"test/mocked/fixture_pages/no_form_login.html",
|
31
|
+
"test/mocked/fixture_pages/no_login_button.html",
|
32
|
+
"test/mocked/fixture_pages/not_logged_in_relay.html",
|
33
|
+
"test/mocked/fixture_pages/sln_does_not_exist.html",
|
34
|
+
"test/mocked/fixture_pages/sln_status.html",
|
35
|
+
"test/mocked/fixture_pages/welcome.html",
|
36
|
+
"test/mocked/myuw_test.rb",
|
37
|
+
"test/mocked/session_test.rb",
|
38
|
+
"test/mocked/sln_test.rb",
|
39
|
+
"test/mocked/test_helper.rb",
|
40
|
+
"test/password.rb.sample"
|
24
41
|
]
|
25
42
|
s.has_rdoc = true
|
26
43
|
s.homepage = %q{http://github.com/mitchellh/rubyuw}
|
@@ -28,6 +45,15 @@ Gem::Specification.new do |s|
|
|
28
45
|
s.require_paths = ["lib"]
|
29
46
|
s.rubygems_version = %q{1.3.1}
|
30
47
|
s.summary = %q{Library which provides a ruby interface to the University of Washington student portal.}
|
48
|
+
s.test_files = [
|
49
|
+
"test/live/session_test.rb",
|
50
|
+
"test/live/sln_test.rb",
|
51
|
+
"test/live/test_helper.rb",
|
52
|
+
"test/mocked/myuw_test.rb",
|
53
|
+
"test/mocked/session_test.rb",
|
54
|
+
"test/mocked/sln_test.rb",
|
55
|
+
"test/mocked/test_helper.rb"
|
56
|
+
]
|
31
57
|
|
32
58
|
if s.respond_to? :specification_version then
|
33
59
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class LiveSessionTest < Test::Unit::TestCase
|
4
|
+
context "live sessions" do
|
5
|
+
setup do
|
6
|
+
@myuw = MyUW.new
|
7
|
+
@session = @myuw.session
|
8
|
+
@session.email = MYUW_ID
|
9
|
+
@session.password = MYUW_PASSWORD
|
10
|
+
end
|
11
|
+
|
12
|
+
should "report successful login with credentials" do
|
13
|
+
assert @session.login
|
14
|
+
end
|
15
|
+
|
16
|
+
should "fail login with bad credentials" do
|
17
|
+
@session.password = MYUW_PASSWORD + "bad"
|
18
|
+
assert !@session.login
|
19
|
+
assert !@session.logged_in?
|
20
|
+
end
|
21
|
+
|
22
|
+
should "report logged_in? once logged in via the session object" do
|
23
|
+
assert @session.login
|
24
|
+
assert @session.logged_in?
|
25
|
+
end
|
26
|
+
|
27
|
+
should "not report logged_in? once logout is called after logging in" do
|
28
|
+
assert @session.login
|
29
|
+
assert @session.logged_in?
|
30
|
+
@session.logout
|
31
|
+
assert !@session.logged_in?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class LiveSLNTest < Test::Unit::TestCase
|
4
|
+
context "live SLN info grabbing" do
|
5
|
+
setup do
|
6
|
+
@myuw = MyUW.new
|
7
|
+
@session = @myuw.session
|
8
|
+
@session.email = MYUW_ID
|
9
|
+
@session.password = MYUW_PASSWORD
|
10
|
+
@session.login
|
11
|
+
assert @session.logged_in? # Sanity
|
12
|
+
|
13
|
+
@sln_info = {
|
14
|
+
:sln => "17237",
|
15
|
+
:quarter => "AUT+2009",
|
16
|
+
:info => {
|
17
|
+
:section => "AE",
|
18
|
+
:type => "QZ",
|
19
|
+
:credits => "",
|
20
|
+
:title => "INTRO TO LOGIC",
|
21
|
+
:current_enrollment => "25",
|
22
|
+
:limit_enrollment => "25",
|
23
|
+
:room_capacity => "40",
|
24
|
+
:space_available => "",
|
25
|
+
:status => "** Closed **",
|
26
|
+
:notes => "Quiz Section"
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
@sln = @myuw.sln(@sln_info[:sln], @sln_info[:quarter])
|
31
|
+
assert @sln # Sanity
|
32
|
+
end
|
33
|
+
|
34
|
+
teardown do
|
35
|
+
sleep(5) # To avoid the timeout problems within tests
|
36
|
+
end
|
37
|
+
|
38
|
+
should "grab course info for a valid SLN and quarter" do
|
39
|
+
@sln_info[:info].each do |k,v|
|
40
|
+
assert_equal v, @sln.send(k), "#{k} should've been #{v}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
should "raise an error if requests are back to back too fast" do
|
45
|
+
assert_nothing_raised do
|
46
|
+
@sln.course
|
47
|
+
end
|
48
|
+
|
49
|
+
@sln.sln = @sln.sln # Force reload
|
50
|
+
|
51
|
+
assert_raise RuntimeError do
|
52
|
+
@sln.course
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
should "raise an error for an invalid SLN/quarter" do
|
57
|
+
@sln.sln = 91919 # Tested to not exist
|
58
|
+
|
59
|
+
assert_raise RuntimeError do
|
60
|
+
@sln.course
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"
|
2
|
+
"http://www.w3.org/TR/REC-html40/strict.dtd">
|
3
|
+
<HTML>
|
4
|
+
<HEAD>
|
5
|
+
<TITLE>Time Schedule Status - Invalid Request
|
6
|
+
</TITLE>
|
7
|
+
<LINK REL="stylesheet" HREF="/home/home.css" TYPE="text/css">
|
8
|
+
|
9
|
+
</HEAD>
|
10
|
+
<BODY>
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
<div><a href="http://www.washington.edu/"><img id="toplogo" src="/home/graphics/UWlogo150p.gif" alt="University of Washington" /></a></div>
|
27
|
+
|
28
|
+
<div id="toolbar">
|
29
|
+
<span class="l1text"><a href="/home/search.html">Search</a> |
|
30
|
+
<a href="/home/directories.html">Directories</a> |
|
31
|
+
<a href="http://www.lib.washington.edu/research/">Reference
|
32
|
+
Tools</a></span></div>
|
33
|
+
|
34
|
+
|
35
|
+
<!--Section Banner Table -->
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
<div class="bannerwrapper">
|
42
|
+
<div id="topbanner" ><img src="/home/graphics/rib0.gif" width="6" height="16" alt="" /></div>
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<div id="crumbs"><span class="l1text"><a href="/">UW Home</a> > <a href="/uwin/">UWIN</a> > <a href="/students/">Student Guide</a></span> </div><div class="forceclear"></div>
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
<H1>Time Schedule Status - Invalid Request</H1>
|
55
|
+
|
56
|
+
|
57
|
+
<p>Your request was improperly formatted. Try again.
|
58
|
+
</P>
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
<div id="footer"><div id="footerseal">
|
66
|
+
<a href="http://www.washington.edu/"><img src="/home/graphics/blockw.gif" width="53" height="37" alt="UW Logo" /></a>
|
67
|
+
</div>
|
68
|
+
<div id="addressright"></div>
|
69
|
+
<div id="address"><address>
|
70
|
+
<a href="http://registrar.washington.edu/">Office of the University Registrar</a>, 206-543-5378<br />
|
71
|
+
<a href="mailto:registrar@u.washington.edu">registrar@u.washington.edu</a><br />
|
72
|
+
Modified: January 28, 1998
|
73
|
+
</address>
|
74
|
+
</div>
|
75
|
+
</div>
|
76
|
+
|
77
|
+
|
78
|
+
</BODY>
|
79
|
+
<HTML>
|
80
|
+
<!--Created by chtml 1.48 on Jun 10, 2009 2:59pm-->
|