rubyuw 0.99.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/README.md +81 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/lib/rubyuw.rb +9 -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/schedule.rb +52 -0
- data/lib/rubyuw/sln.rb +108 -0
- data/rubyuw.gemspec +97 -0
- data/test/live/curriculum_enrollment_test.rb +47 -0
- data/test/live/sln_test.rb +54 -0
- data/test/live/test_helper.rb +24 -0
- data/test/mocked/base_test.rb +171 -0
- data/test/mocked/connection_test.rb +178 -0
- data/test/mocked/curriculum_enrollment_test.rb +158 -0
- data/test/mocked/fixture_pages/bad_request.html +80 -0
- data/test/mocked/fixture_pages/curric_courses.html +2659 -0
- data/test/mocked/fixture_pages/curric_no_courses.html +88 -0
- data/test/mocked/fixture_pages/curric_no_curric_found.html +86 -0
- data/test/mocked/fixture_pages/logged_in_relay.html +660 -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/registration.html +699 -0
- data/test/mocked/fixture_pages/registration_error.html +699 -0
- data/test/mocked/fixture_pages/registration_error_no_rows.html +627 -0
- data/test/mocked/fixture_pages/registration_no_form.html +700 -0
- data/test/mocked/fixture_pages/registration_success.html +718 -0
- data/test/mocked/fixture_pages/registration_unknown.html +721 -0
- data/test/mocked/fixture_pages/schedule.html +166 -0
- data/test/mocked/fixture_pages/schedule_diff_credit_row.html +167 -0
- data/test/mocked/fixture_pages/schedule_empty.html +130 -0
- data/test/mocked/fixture_pages/schedule_no_credit_row.html +169 -0
- data/test/mocked/fixture_pages/sln_does_not_exist.html +85 -0
- data/test/mocked/fixture_pages/sln_no_course_info.html +94 -0
- data/test/mocked/fixture_pages/sln_no_course_notes.html +94 -0
- data/test/mocked/fixture_pages/sln_no_enrollment_info.html +94 -0
- data/test/mocked/fixture_pages/sln_status.html +92 -0
- data/test/mocked/fixture_pages/welcome.html +93 -0
- data/test/mocked/schedule_test.rb +89 -0
- data/test/mocked/sln_test.rb +146 -0
- data/test/mocked/test_helper.rb +67 -0
- data/test/password.rb.sample +8 -0
- metadata +118 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module RubyUW
|
2
|
+
module Errors
|
3
|
+
# Exception signaling an invalid page was encountered.
|
4
|
+
class InvalidPageError < Exception
|
5
|
+
attr_reader :page
|
6
|
+
|
7
|
+
def initialize(page, msg=nil)
|
8
|
+
@page = page
|
9
|
+
super(msg)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Exception when login is required and not logged in.
|
14
|
+
class NotLoggedInError < Exception; end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module RubyUW
|
2
|
+
# RubyUW::Schedule is used to grab the schedule of the
|
3
|
+
# authenticated user.
|
4
|
+
#
|
5
|
+
# <b>Requires authentication with RubyUW::Base prior to use!</b>
|
6
|
+
class Schedule
|
7
|
+
class << self
|
8
|
+
# Scrapes the authenticated user's schedule from MyUW.
|
9
|
+
# Authentication is required prior to use.
|
10
|
+
#
|
11
|
+
# Returns an array of {RubyUW::SLN} objects. The {RubyUW::SLN}
|
12
|
+
# object responds to the following data elements:
|
13
|
+
#
|
14
|
+
# * sln
|
15
|
+
# * course
|
16
|
+
# * type
|
17
|
+
# * credits
|
18
|
+
# * title
|
19
|
+
# * days
|
20
|
+
# * time
|
21
|
+
# * location
|
22
|
+
# * instructor
|
23
|
+
#
|
24
|
+
# To get any more details you have to use the {RubyUW::SLN.find}
|
25
|
+
# method.
|
26
|
+
def get
|
27
|
+
raise Errors::NotLoggedInError.new unless Base.authenticated?
|
28
|
+
|
29
|
+
page = Base.connection.get("https://sdb.admin.washington.edu/students/uwnetid/schedule.asp")
|
30
|
+
extract_courses(page)
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def extract_courses(page)
|
36
|
+
course_nodes = page.search("//table[@border=1 and @cellpadding=3]//tr[@bgcolor='#d0d0d0'][2]/following-sibling::*//td[@align='center']/parent::*")
|
37
|
+
courses = []
|
38
|
+
course_nodes.each do |node|
|
39
|
+
course = extract_course(node)
|
40
|
+
courses << RubyUW::SLN.new(course[:sln], nil, course)
|
41
|
+
end
|
42
|
+
|
43
|
+
courses
|
44
|
+
end
|
45
|
+
|
46
|
+
def extract_course(node)
|
47
|
+
keys = [:sln, :course, :type, :credits, :title, :days, :time, :location, :instructor]
|
48
|
+
Base.extract(node, 'td', keys)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/rubyuw/sln.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
module RubyUW
|
2
|
+
# RubyUW::SLN is used to find and extract information about
|
3
|
+
# specific SLNs. It grabs SLN information via the MyUW time
|
4
|
+
# schedule.
|
5
|
+
#
|
6
|
+
# <b>Requires authentication with RubyUW::Base prior to use!</b>
|
7
|
+
#
|
8
|
+
# Additionally, this class should typically never be instantiated
|
9
|
+
# by any developer. Instead, the class methods should be used
|
10
|
+
# which will return an instance of the SLN object.
|
11
|
+
#
|
12
|
+
# == Usage Examples
|
13
|
+
#
|
14
|
+
# require 'rubyuw'
|
15
|
+
# RubyUW::Base.authenticate("netid", "password")
|
16
|
+
# sln_info = RubyUW::SLN.find("12345", "AUT+2009")
|
17
|
+
class SLN
|
18
|
+
attr_reader :sln
|
19
|
+
attr_reader :data
|
20
|
+
|
21
|
+
# Initialize a new SLN object. This should never be called on its
|
22
|
+
# own, but instead you should use the find method to setup a
|
23
|
+
# sln.
|
24
|
+
#
|
25
|
+
# @param [String] SLN number in string format.
|
26
|
+
# @param [String] Term that the SLN corresponds to.
|
27
|
+
# @param [Hash] Data of key to value.
|
28
|
+
def initialize(sln, term, data)
|
29
|
+
@sln = sln
|
30
|
+
@term = term
|
31
|
+
@data = data
|
32
|
+
end
|
33
|
+
|
34
|
+
# Grab the data of the SLN based on a key. Returns the value of
|
35
|
+
# a field for the SLN, or nil otherwise. Supported fields coming
|
36
|
+
# soon.
|
37
|
+
def data(key)
|
38
|
+
@data[key.to_sym]
|
39
|
+
end
|
40
|
+
|
41
|
+
class <<self
|
42
|
+
# Finds information about a specific SLN. The SLN information
|
43
|
+
# is grabbed from the MyUW time schedule page. Authentication is
|
44
|
+
# required prior to use.
|
45
|
+
#
|
46
|
+
# @param [#to_s] The SLN, which must respond to #to_s
|
47
|
+
# @param [String] The term, as represented in the time schedule URL.
|
48
|
+
# @return [RubyUW::SLN]
|
49
|
+
def find(sln_number, term)
|
50
|
+
raise Errors::NotLoggedInError.new unless Base.authenticated?
|
51
|
+
|
52
|
+
# Grab the SLN page and check for various failure scenarios
|
53
|
+
page = Base.connection.get("https://sdb.admin.washington.edu/timeschd/uwnetid/sln.asp?QTRYR=#{term}&SLN=#{sln_number}")
|
54
|
+
raise Errors::SLNRequestedTooSoonError.new if requested_too_soon?(page)
|
55
|
+
raise Errors::SLNDoesNotExistError.new if !sln_exists?(page)
|
56
|
+
raise Errors::SLNServiceClosedError.new if time_schedule_closed?(page)
|
57
|
+
|
58
|
+
# Now that the page is probably valid, we extract the field data
|
59
|
+
# based on various xpaths.
|
60
|
+
new(sln_number, term, extract_data_from_page(page))
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def requested_too_soon?(page)
|
66
|
+
page.uri.to_s == "http://www.washington.edu/students/timeschd/badrequest.html"
|
67
|
+
end
|
68
|
+
|
69
|
+
def sln_exists?(page)
|
70
|
+
page.body.to_s.match(/SLN: (.{5,6}) does not exist./).nil?
|
71
|
+
end
|
72
|
+
|
73
|
+
def time_schedule_closed?(page)
|
74
|
+
page.uri.to_s == "http://www.washington.edu/students/timeschd/nots.html"
|
75
|
+
end
|
76
|
+
|
77
|
+
def extract_data_from_page(page)
|
78
|
+
extract_data = [
|
79
|
+
[[nil, :course, :section, :type, :credits, :title], "//table[@border=1 and @cellpadding=3]//tr[@rowspan=1]//td"],
|
80
|
+
[[:current_enrollment, :limit_enrollment, :room_capacity, :space_available, :status], "//table[@border=1 and @cellpadding=3]//tr[count(td)=5]//td"],
|
81
|
+
[[:notes], "//table[@border=1 and @cellpadding=3]//tr[count(td)=1]//preceding-sibling::tr[count(th)=1]//following-sibling::tr//td"]
|
82
|
+
]
|
83
|
+
|
84
|
+
data = {}
|
85
|
+
extract_data.each do |keys, xpath|
|
86
|
+
data.merge!(Base.extract(page, xpath, keys))
|
87
|
+
end
|
88
|
+
|
89
|
+
data
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
module Errors
|
95
|
+
# An error indicating that an SLN was requested back-to-back too
|
96
|
+
# quickly. MyUW enforces a timeout between requests for SLN information,
|
97
|
+
# and when this is not obeyed, this error will be thrown by RubyUW.
|
98
|
+
class SLNRequestedTooSoonError < Exception; end
|
99
|
+
|
100
|
+
# An error indicating that an SLN requested is invalid (does not
|
101
|
+
# exist).
|
102
|
+
class SLNDoesNotExistError < Exception; end
|
103
|
+
|
104
|
+
# An error indicating that the time scheduling service of MyUW
|
105
|
+
# is currently closed.
|
106
|
+
class SLNServiceClosedError < Exception; end
|
107
|
+
end
|
108
|
+
end
|
data/rubyuw.gemspec
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rubyuw}
|
8
|
+
s.version = "0.99.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Mitchell Hashimoto"]
|
12
|
+
s.date = %q{2009-12-06}
|
13
|
+
s.description = %q{Library which provides a ruby interface to the University of Washington student portal.}
|
14
|
+
s.email = %q{mitchell.hashimoto@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"README.md",
|
21
|
+
"Rakefile",
|
22
|
+
"VERSION",
|
23
|
+
"lib/rubyuw.rb",
|
24
|
+
"lib/rubyuw/base.rb",
|
25
|
+
"lib/rubyuw/connection.rb",
|
26
|
+
"lib/rubyuw/curriculum_enrollment.rb",
|
27
|
+
"lib/rubyuw/errors.rb",
|
28
|
+
"lib/rubyuw/schedule.rb",
|
29
|
+
"lib/rubyuw/sln.rb",
|
30
|
+
"rubyuw.gemspec",
|
31
|
+
"test/live/curriculum_enrollment_test.rb",
|
32
|
+
"test/live/sln_test.rb",
|
33
|
+
"test/live/test_helper.rb",
|
34
|
+
"test/mocked/base_test.rb",
|
35
|
+
"test/mocked/connection_test.rb",
|
36
|
+
"test/mocked/curriculum_enrollment_test.rb",
|
37
|
+
"test/mocked/fixture_pages/bad_request.html",
|
38
|
+
"test/mocked/fixture_pages/curric_courses.html",
|
39
|
+
"test/mocked/fixture_pages/curric_no_courses.html",
|
40
|
+
"test/mocked/fixture_pages/curric_no_curric_found.html",
|
41
|
+
"test/mocked/fixture_pages/logged_in_relay.html",
|
42
|
+
"test/mocked/fixture_pages/login.html",
|
43
|
+
"test/mocked/fixture_pages/no_form_login.html",
|
44
|
+
"test/mocked/fixture_pages/no_login_button.html",
|
45
|
+
"test/mocked/fixture_pages/not_logged_in_relay.html",
|
46
|
+
"test/mocked/fixture_pages/registration.html",
|
47
|
+
"test/mocked/fixture_pages/registration_error.html",
|
48
|
+
"test/mocked/fixture_pages/registration_error_no_rows.html",
|
49
|
+
"test/mocked/fixture_pages/registration_no_form.html",
|
50
|
+
"test/mocked/fixture_pages/registration_success.html",
|
51
|
+
"test/mocked/fixture_pages/registration_unknown.html",
|
52
|
+
"test/mocked/fixture_pages/schedule.html",
|
53
|
+
"test/mocked/fixture_pages/schedule_diff_credit_row.html",
|
54
|
+
"test/mocked/fixture_pages/schedule_empty.html",
|
55
|
+
"test/mocked/fixture_pages/schedule_no_credit_row.html",
|
56
|
+
"test/mocked/fixture_pages/sln_does_not_exist.html",
|
57
|
+
"test/mocked/fixture_pages/sln_no_course_info.html",
|
58
|
+
"test/mocked/fixture_pages/sln_no_course_notes.html",
|
59
|
+
"test/mocked/fixture_pages/sln_no_enrollment_info.html",
|
60
|
+
"test/mocked/fixture_pages/sln_status.html",
|
61
|
+
"test/mocked/fixture_pages/welcome.html",
|
62
|
+
"test/mocked/schedule_test.rb",
|
63
|
+
"test/mocked/sln_test.rb",
|
64
|
+
"test/mocked/test_helper.rb",
|
65
|
+
"test/password.rb.sample"
|
66
|
+
]
|
67
|
+
s.homepage = %q{http://github.com/mitchellh/rubyuw}
|
68
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
69
|
+
s.require_paths = ["lib"]
|
70
|
+
s.rubygems_version = %q{1.3.5}
|
71
|
+
s.summary = %q{Library which provides a ruby interface to the University of Washington student portal.}
|
72
|
+
s.test_files = [
|
73
|
+
"test/live/curriculum_enrollment_test.rb",
|
74
|
+
"test/live/sln_test.rb",
|
75
|
+
"test/live/test_helper.rb",
|
76
|
+
"test/mocked/base_test.rb",
|
77
|
+
"test/mocked/connection_test.rb",
|
78
|
+
"test/mocked/curriculum_enrollment_test.rb",
|
79
|
+
"test/mocked/schedule_test.rb",
|
80
|
+
"test/mocked/sln_test.rb",
|
81
|
+
"test/mocked/test_helper.rb"
|
82
|
+
]
|
83
|
+
|
84
|
+
if s.respond_to? :specification_version then
|
85
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
86
|
+
s.specification_version = 3
|
87
|
+
|
88
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
89
|
+
s.add_runtime_dependency(%q<tenderlove-mechanize>, [">= 0.9.3.20090623142847"])
|
90
|
+
else
|
91
|
+
s.add_dependency(%q<tenderlove-mechanize>, [">= 0.9.3.20090623142847"])
|
92
|
+
end
|
93
|
+
else
|
94
|
+
s.add_dependency(%q<tenderlove-mechanize>, [">= 0.9.3.20090623142847"])
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class LiveCurriculumEnrollmentTest < Test::Unit::TestCase
|
4
|
+
context "live curriculum info grabbing" do
|
5
|
+
setup do
|
6
|
+
@curriculum = "CHEM"
|
7
|
+
@term = "AUT+2009"
|
8
|
+
|
9
|
+
@curriculum_info = {
|
10
|
+
"11633" => {
|
11
|
+
:course => "CHEM 110",
|
12
|
+
:section => "A",
|
13
|
+
:type => "LC"
|
14
|
+
},
|
15
|
+
"11635" => {
|
16
|
+
:course => "CHEM 110",
|
17
|
+
:section => "AB",
|
18
|
+
:type => "QZ"
|
19
|
+
},
|
20
|
+
"11826" => {
|
21
|
+
:course => "CHEM 475",
|
22
|
+
:section => "A",
|
23
|
+
:type => "LC"
|
24
|
+
}
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
should "raise an error for an invalid SLN/quarter" do
|
29
|
+
authenticate
|
30
|
+
assert_raise(RubyUW::Errors::CurriculumDoesNotExistError) { RubyUW::CurriculumEnrollment.find("NOTCHEM", @term) }
|
31
|
+
end
|
32
|
+
|
33
|
+
should "grab all SLNs properly" do
|
34
|
+
authenticate
|
35
|
+
results = RubyUW::CurriculumEnrollment.find(@curriculum, @term)
|
36
|
+
|
37
|
+
@curriculum_info.each do |sln, info|
|
38
|
+
course = results[sln]
|
39
|
+
assert !course.nil?
|
40
|
+
|
41
|
+
info.each do |k,v|
|
42
|
+
assert_equal v, course.data(k)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,54 @@
|
|
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
|
+
@sln_info = {
|
7
|
+
:sln => "17237",
|
8
|
+
:quarter => "AUT+2009",
|
9
|
+
:info => {
|
10
|
+
:section => "AE",
|
11
|
+
:type => "QZ",
|
12
|
+
:credits => "",
|
13
|
+
:title => "INTRO TO LOGIC",
|
14
|
+
:current_enrollment => "25",
|
15
|
+
:limit_enrollment => "25",
|
16
|
+
:room_capacity => "40",
|
17
|
+
:space_available => "",
|
18
|
+
:status => "** Closed **",
|
19
|
+
:notes => "Quiz Section"
|
20
|
+
}
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
teardown do
|
25
|
+
sleep(5)
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_sln_obj
|
29
|
+
RubyUW::SLN.find(@sln_info[:sln], @sln_info[:quarter])
|
30
|
+
end
|
31
|
+
|
32
|
+
should "grab course info for a valid SLN" do
|
33
|
+
authenticate
|
34
|
+
@sln = get_sln_obj
|
35
|
+
|
36
|
+
@sln_info[:info].each do |k,v|
|
37
|
+
assert_equal v, @sln.data(k), "#{k} should've been #{v}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
should "raise an error if requests are back to back too fast" do
|
42
|
+
authenticate
|
43
|
+
assert_nothing_raised() { get_sln_obj }
|
44
|
+
assert_raise(RubyUW::Errors::SLNRequestedTooSoonError) { get_sln_obj }
|
45
|
+
end
|
46
|
+
|
47
|
+
should "raise an error for an invalid SLN/quarter" do
|
48
|
+
@sln_info[:sln] = 91919 # Tested to not exist
|
49
|
+
|
50
|
+
authenticate
|
51
|
+
assert_raise(RubyUW::Errors::SLNDoesNotExistError) { get_sln_obj }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,24 @@
|
|
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
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
|
9
|
+
require 'rubyuw'
|
10
|
+
require 'test/unit'
|
11
|
+
require "contest"
|
12
|
+
require "stories"
|
13
|
+
require "mocha"
|
14
|
+
|
15
|
+
class Test::Unit::TestCase
|
16
|
+
def setup
|
17
|
+
RubyUW::Base.reset_connection!
|
18
|
+
end
|
19
|
+
|
20
|
+
def authenticate
|
21
|
+
assert RubyUW::Base.authenticate(MYUW_ID, MYUW_PASSWORD)
|
22
|
+
assert RubyUW::Base.authenticated? # sanity
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class BaseTest < Test::Unit::TestCase
|
4
|
+
context "with connection" do
|
5
|
+
setup do
|
6
|
+
RubyUW::Base.reset_connection!
|
7
|
+
end
|
8
|
+
|
9
|
+
should "initialize a WWW::Mechanize object on the first call" do
|
10
|
+
RubyUW::Connection.expects(:new).once
|
11
|
+
RubyUW::Base.connection
|
12
|
+
end
|
13
|
+
|
14
|
+
should "not initialize WWW::Mechanize objects on subsequent calls" do
|
15
|
+
RubyUW::Connection.expects(:new).once.returns(true)
|
16
|
+
50.times { RubyUW::Base.connection }
|
17
|
+
end
|
18
|
+
|
19
|
+
should "clear out the connection to force recreation of object on reset_connection!" do
|
20
|
+
assert !RubyUW::Base.connection.nil?
|
21
|
+
RubyUW::Base.reset_connection!
|
22
|
+
RubyUW::Connection.expects(:new).once.returns(true)
|
23
|
+
50.times { RubyUW::Base.connection }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "with authentication" do
|
28
|
+
setup do
|
29
|
+
# initialize the base connection
|
30
|
+
@conn = RubyUW::Base.reset_connection!.connection
|
31
|
+
@conn.follow_meta_refresh = false
|
32
|
+
|
33
|
+
@good_id = "foo"
|
34
|
+
@good_password = "bar"
|
35
|
+
@bad_password = @good_password + "wrong"
|
36
|
+
end
|
37
|
+
|
38
|
+
should "clear the cookie jar of the connection" do
|
39
|
+
RubyUW::Base.expects(:logout).once
|
40
|
+
|
41
|
+
result_page = mock('page')
|
42
|
+
result_page.stubs(:form_with).returns(true)
|
43
|
+
@conn.stubs(:execute!).returns(result_page)
|
44
|
+
|
45
|
+
RubyUW::Base.authenticate(@good_id, @good_password)
|
46
|
+
end
|
47
|
+
|
48
|
+
should "succeed login with good ID and password and valid pages" do
|
49
|
+
mock_flow([
|
50
|
+
[:goto, ["http://myuw.washington.edu"], "welcome"],
|
51
|
+
[:submit_form, ["f", anything], "not_logged_in_relay"],
|
52
|
+
[:submit_form, ["relay", anything], "login"],
|
53
|
+
[:submit_form, ["query", anything], "not_logged_in_relay"]
|
54
|
+
])
|
55
|
+
|
56
|
+
assert RubyUW::Base.authenticate(@good_id, @good_password)
|
57
|
+
end
|
58
|
+
|
59
|
+
should "fail login with bad ID and password" do
|
60
|
+
mock_flow([
|
61
|
+
[:goto, ["http://myuw.washington.edu"], "welcome"],
|
62
|
+
[:submit_form, ["f", anything], "not_logged_in_relay"],
|
63
|
+
[:submit_form, ["relay", anything], "login"],
|
64
|
+
[:submit_form, ["query", anything], "login"]
|
65
|
+
])
|
66
|
+
|
67
|
+
assert !RubyUW::Base.authenticate(@good_id, @bad_password)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "with authentication check" do
|
72
|
+
should "return true if it can find the correct xpath" do
|
73
|
+
mock_flow([
|
74
|
+
[:goto, ["http://myuw.washington.edu"], "welcome"],
|
75
|
+
[:submit_form, ["f", anything], "logged_in_relay"]
|
76
|
+
])
|
77
|
+
|
78
|
+
assert RubyUW::Base.authenticated?
|
79
|
+
end
|
80
|
+
|
81
|
+
should "not return true if it can't find the greeting" do
|
82
|
+
mock_flow([
|
83
|
+
[:goto, ["http://myuw.washington.edu"], "welcome"],
|
84
|
+
[:submit_form, ["f", anything], "login"]
|
85
|
+
])
|
86
|
+
|
87
|
+
assert !RubyUW::Base.authenticated?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "with logging out" do
|
92
|
+
should "clear the cookie jar on logout" do
|
93
|
+
cookie_jar = mock('cookie_jar')
|
94
|
+
cookie_jar.expects(:clear!).once
|
95
|
+
|
96
|
+
conn = RubyUW::Base.connection
|
97
|
+
conn.expects(:cookie_jar).returns(cookie_jar)
|
98
|
+
|
99
|
+
RubyUW::Base.logout
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "with extracting data from page" do
|
104
|
+
setup do
|
105
|
+
@page = mock('page')
|
106
|
+
@xpath = "foo"
|
107
|
+
end
|
108
|
+
|
109
|
+
def setup_nodes(inner_texts)
|
110
|
+
nodes = []
|
111
|
+
inner_texts.each do |text|
|
112
|
+
node = mock('node')
|
113
|
+
node.stubs(:inner_text).returns(text)
|
114
|
+
|
115
|
+
nodes << node
|
116
|
+
end
|
117
|
+
|
118
|
+
nodes
|
119
|
+
end
|
120
|
+
|
121
|
+
should "run the xpath on the specified page" do
|
122
|
+
@page.expects(:search).with(@xpath).returns([])
|
123
|
+
RubyUW::Base.extract(@page, @xpath, [])
|
124
|
+
end
|
125
|
+
|
126
|
+
should "return nil if the xpath returns an empty array" do
|
127
|
+
@page.expects(:search).with(@xpath).returns([])
|
128
|
+
assert_nil RubyUW::Base.extract(@page, @xpath, [])
|
129
|
+
end
|
130
|
+
|
131
|
+
should "map keys to node results if xpath returns valid nodes" do
|
132
|
+
expected = { :one => "1", :two => "2", :three => "3" }
|
133
|
+
nodes = setup_nodes(expected.values)
|
134
|
+
keys = expected.keys
|
135
|
+
|
136
|
+
@page.expects(:search).with(@xpath).returns(nodes)
|
137
|
+
result = RubyUW::Base.extract(@page, @xpath, keys)
|
138
|
+
assert expected == result
|
139
|
+
end
|
140
|
+
|
141
|
+
should "skip the nil keys (while continuing to go through the node list)" do
|
142
|
+
nodes = setup_nodes(["1","2","3"])
|
143
|
+
keys = [:one, nil, :three]
|
144
|
+
|
145
|
+
@page.expects(:search).with(@xpath).returns(nodes)
|
146
|
+
result = RubyUW::Base.extract(@page, @xpath, keys)
|
147
|
+
assert_equal 2, result.length
|
148
|
+
assert !result.value?("2")
|
149
|
+
end
|
150
|
+
|
151
|
+
should "return what it has so far if it runs out of keys" do
|
152
|
+
nodes = setup_nodes((1..50).to_a.map(&:to_s))
|
153
|
+
keys = [:one]
|
154
|
+
|
155
|
+
@page.expects(:search).with(@xpath).returns(nodes)
|
156
|
+
result = RubyUW::Base.extract(@page, @xpath, keys)
|
157
|
+
assert_equal keys.length, result.length
|
158
|
+
assert_equal "1", result[keys[0]]
|
159
|
+
end
|
160
|
+
|
161
|
+
should "get rid of odd characters that MyUW has in text" do
|
162
|
+
nodes = setup_nodes(["foo\302\240"])
|
163
|
+
keys = [:one]
|
164
|
+
|
165
|
+
@page.expects(:search).with(@xpath).returns(nodes)
|
166
|
+
result = RubyUW::Base.extract(@page, @xpath, keys)
|
167
|
+
assert_equal keys.length, result.length
|
168
|
+
assert_equal "foo", result[keys[0]]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|