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 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.0
1
+ 0.4.1
data/lib/myuw.rb CHANGED
@@ -8,11 +8,7 @@ require 'myuw/sln'
8
8
 
9
9
  class MyUW
10
10
  extend Forwardable
11
-
12
- ##
13
- # The version of the ruby MyUW automation class
14
- VERSION = '0.1'
15
-
11
+
16
12
  attr_accessor :browser
17
13
  attr_accessor :session
18
14
 
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 = @myuw.browser.get("http://myuw.washington.edu")
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
- welcome_msg = relay_page.search("span.greetingw")
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
- def get_login_page
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" then
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
- # Get the SLN information. I use a pretty sneaky xpath query
41
- # here which is VERY LIKELY to break given any changes to the
42
- # design of UW time schedules
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
- # Get the enrollment information. Uses a pretty complicated
58
- # xpath query once again which is likely to break given any
59
- # changes to the design of the UW time schedules.
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
- # And finally getting the notes of an SLN, accompanied by
75
- # by far the ugliest xpath query yet.
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.0"
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-26}
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,5 @@
1
+ begin
2
+ require File.join(File.dirname(__FILE__), '..', 'password')
3
+ rescue LoadError => e
4
+ abort("Running live tests requires a password.rb file in the test folder with your MyUW credentials.")
5
+ 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
+ &nbsp;<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> &gt; <a href="/uwin/">UWIN</a> &gt; <a href="/students/">Student Guide</a></span>&nbsp;</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-->