rubyuw 0.99.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 +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
|