google_apps_oauth2 0.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.
- checksums.yaml +15 -0
- data/.gitignore +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +40 -0
- data/LICENSE +10 -0
- data/README.md +461 -0
- data/Rakefile +5 -0
- data/google_apps_oauth2.gemspec +20 -0
- data/lib/google_apps_oauth2.rb +7 -0
- data/lib/google_apps_oauth2/atom/atom.rb +26 -0
- data/lib/google_apps_oauth2/atom/document.rb +46 -0
- data/lib/google_apps_oauth2/atom/feed.rb +90 -0
- data/lib/google_apps_oauth2/atom/node.rb +57 -0
- data/lib/google_apps_oauth2/atom/user.rb +21 -0
- data/lib/google_apps_oauth2/parsers/feed_parser.rb +15 -0
- data/lib/google_apps_oauth2/transport.rb +57 -0
- data/spec/fixtures/invalid_token.html +10 -0
- data/spec/fixtures/refreshed_token_response.json +6 -0
- data/spec/fixtures/user.xml +5 -0
- data/spec/fixtures/users_feed.xml +49 -0
- data/spec/google_apps_oauth2/atom/document_spec.rb +18 -0
- data/spec/google_apps_oauth2/atom/feed_spec.rb +78 -0
- data/spec/google_apps_oauth2/atom/node_spec.rb +34 -0
- data/spec/google_apps_oauth2/atom/user_spec.rb +41 -0
- data/spec/google_apps_oauth2/parsers/feed_parser_spec.rb +16 -0
- data/spec/google_apps_oauth2/transport_spec.rb +46 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/web_stubs.rb +16 -0
- metadata +153 -0
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = 'google_apps_oauth2'
|
3
|
+
spec.version = '0.1'
|
4
|
+
spec.license = "MIT"
|
5
|
+
spec.summary = 'Google Apps APIs using OAuth2'
|
6
|
+
spec.description = 'Library for interfacing with Google Apps Domain and Application APIs via OAuth2'
|
7
|
+
spec.authors = ['Will Read']
|
8
|
+
spec.files = Dir.glob(File.join('**', 'lib', '**', '*.rb'))
|
9
|
+
spec.homepage = 'https://github.com/TildeWill/google_apps'
|
10
|
+
|
11
|
+
spec.add_dependency('libxml-ruby', '>= 2.2.2')
|
12
|
+
spec.add_dependency('httparty')
|
13
|
+
|
14
|
+
spec.add_development_dependency 'rake'
|
15
|
+
spec.add_development_dependency 'rspec'
|
16
|
+
spec.add_development_dependency 'webmock'
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split("\n")
|
19
|
+
spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
require 'google_apps_oauth2/parsers/feed_parser'
|
2
|
+
require 'google_apps_oauth2/transport'
|
3
|
+
require 'google_apps_oauth2/atom/atom'
|
4
|
+
require 'google_apps_oauth2/atom/node'
|
5
|
+
require 'google_apps_oauth2/atom/document'
|
6
|
+
require 'google_apps_oauth2/atom/feed'
|
7
|
+
require 'google_apps_oauth2/atom/user'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'libxml'
|
2
|
+
|
3
|
+
module GoogleAppsOauth2
|
4
|
+
module Atom
|
5
|
+
include LibXML
|
6
|
+
|
7
|
+
NAMESPACES = {
|
8
|
+
atom: 'http://www.w3.org/2005/Atom',
|
9
|
+
apps: 'http://schemas.google.com/apps/2006',
|
10
|
+
gd: 'http://schemas.google.com/g/2005',
|
11
|
+
openSearch: 'http://a9.com/-/spec/opensearchrss/1.0/'
|
12
|
+
}
|
13
|
+
|
14
|
+
CATEGORY = {
|
15
|
+
user: [['scheme', 'http://schemas.google.com/g/2005#kind'], ['term', 'http://schemas.google.com/apps/2006#user']],
|
16
|
+
}
|
17
|
+
|
18
|
+
ENTRY_TAG = ["<atom:entry xmlns:atom=\"#{NAMESPACES[:atom]}\" xmlns:apps=\"#{NAMESPACES[:apps]}\" xmlns:gd=\"#{NAMESPACES[:gd]}\">", '</atom:entry>']
|
19
|
+
|
20
|
+
def user(*args)
|
21
|
+
User.new *args
|
22
|
+
end
|
23
|
+
|
24
|
+
module_function :user
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module GoogleAppsOauth2
|
2
|
+
module Atom
|
3
|
+
class Document
|
4
|
+
include Node
|
5
|
+
|
6
|
+
def initialize(doc, map = {})
|
7
|
+
@doc = parse(doc)
|
8
|
+
@map = map
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse(xml)
|
12
|
+
document = Atom::XML::Document.string(xml)
|
13
|
+
Atom::XML::Parser.document(document).parse
|
14
|
+
end
|
15
|
+
|
16
|
+
# find_values searches @doc and assigns any values
|
17
|
+
# to their corresponding instance variables. This is
|
18
|
+
# useful when we've been given a string of XML and need
|
19
|
+
# internal consistency in the object.
|
20
|
+
#
|
21
|
+
# find_values
|
22
|
+
def find_values
|
23
|
+
@doc.root.each do |entry|
|
24
|
+
intersect = @map.keys & entry.attributes.to_h.keys.map(&:to_sym)
|
25
|
+
set_instances(intersect, entry) unless intersect.empty?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sets instance variables in the current object based on
|
30
|
+
# values found in the XML document and the mapping specified
|
31
|
+
# in GoogleApps::Atom::MAPS
|
32
|
+
|
33
|
+
# @param [Array] intersect
|
34
|
+
# @param [LibXML::XML::Node] node
|
35
|
+
# @param [Hash] map
|
36
|
+
#
|
37
|
+
# @visibility public
|
38
|
+
# @return
|
39
|
+
def set_instances(intersect, node)
|
40
|
+
intersect.each do |attribute|
|
41
|
+
instance_variable_set "@#{@map[attribute]}", check_value(node.attributes[attribute])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module GoogleAppsOauth2
|
2
|
+
module Atom
|
3
|
+
class Feed < Document
|
4
|
+
attr_reader :doc, :items, :next_page
|
5
|
+
def initialize(body)
|
6
|
+
super(body)
|
7
|
+
|
8
|
+
@items = entries_from(document: @doc, entry_tag: 'entry')
|
9
|
+
end
|
10
|
+
|
11
|
+
def entries_from(properties)
|
12
|
+
properties[:document].root.inject([]) do |results, entry|
|
13
|
+
if entry.name == properties[:entry_tag]
|
14
|
+
results << new_doc(node_to_ary(entry), ['apps:', 'atom:', 'gd:'])
|
15
|
+
end
|
16
|
+
set_next_page(entry) if entry.name == 'link' and entry.attributes[:rel] == 'next'
|
17
|
+
results
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def set_next_page(node)
|
22
|
+
@next_page = node.attributes[:href]
|
23
|
+
end
|
24
|
+
|
25
|
+
# node_to_ary converts a Atom::XML::Node to an array.
|
26
|
+
#
|
27
|
+
# node_to_ary node
|
28
|
+
#
|
29
|
+
# node_to_ary returns the string representation of the
|
30
|
+
# given node split on \n.
|
31
|
+
def node_to_ary(node)
|
32
|
+
node.to_s.split("\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
# new_doc creates a new Atom document from the data
|
36
|
+
# provided in the feed. new_doc takes a type, an
|
37
|
+
# array of content to be placed into the document
|
38
|
+
# as well as an array of filters.
|
39
|
+
#
|
40
|
+
# new_doc 'user', content_array, ['apps:']
|
41
|
+
#
|
42
|
+
# new_doc returns an GoogleApps::Atom document of the
|
43
|
+
# specified type.
|
44
|
+
def new_doc(content_array, filters)
|
45
|
+
content_array = filters.map do |filter|
|
46
|
+
grab_elements(content_array, filter)
|
47
|
+
end
|
48
|
+
|
49
|
+
add_category(content_array)
|
50
|
+
|
51
|
+
Atom.send :user, entry_wrap(content_array.flatten).join("\n")
|
52
|
+
end
|
53
|
+
|
54
|
+
# add_category adds the proper atom:category node to the
|
55
|
+
# content_array
|
56
|
+
#
|
57
|
+
# add_category content_array, 'user'
|
58
|
+
#
|
59
|
+
# add_category returns the modified content_array
|
60
|
+
def add_category(content_array)
|
61
|
+
content_array.unshift(create_node(type: 'atom:category', attrs: Atom::CATEGORY[:user]).to_s)
|
62
|
+
end
|
63
|
+
|
64
|
+
# grab_elements applies the specified filter to the
|
65
|
+
# provided array. Google's feed provides a lot of data
|
66
|
+
# that we don't need in an entry document.
|
67
|
+
#
|
68
|
+
# grab_elements content_array, 'apps:'
|
69
|
+
#
|
70
|
+
# grab_elements returns an array of items from content_array
|
71
|
+
# that match the given filter.
|
72
|
+
def grab_elements(content_array, filter)
|
73
|
+
content_array.grep(Regexp.new filter)
|
74
|
+
end
|
75
|
+
|
76
|
+
# entry_wrap adds atom:entry opening and closing tags
|
77
|
+
# to the provided content_array and the beginning and
|
78
|
+
# end.
|
79
|
+
#
|
80
|
+
# entry_wrap content_array
|
81
|
+
#
|
82
|
+
# entry_wrap returns an array with an opening atom:entry
|
83
|
+
# element prepended to the front and a closing atom:entry
|
84
|
+
# tag appended to the end.
|
85
|
+
def entry_wrap(content_array)
|
86
|
+
content_array.unshift(Atom::ENTRY_TAG[0]).push(Atom::ENTRY_TAG[1])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module GoogleAppsOauth2
|
2
|
+
module Atom
|
3
|
+
module Node
|
4
|
+
|
5
|
+
# create_node takes a hash of properties from which to
|
6
|
+
# build the XML node. The properties hash must have
|
7
|
+
# a :type key, it is also possible to pass an :attrs
|
8
|
+
# key with an array of attribute name, value pairs.
|
9
|
+
#
|
10
|
+
# create_node type: 'apps:property', attrs: [['name', 'Tim'], ['userName', 'tim@bob.com']]
|
11
|
+
#
|
12
|
+
# create_node returns an Atom::XML::Node with the specified
|
13
|
+
# properties.
|
14
|
+
def create_node(properties)
|
15
|
+
if properties[:attrs]
|
16
|
+
add_attributes Atom::XML::Node.new(properties[:type]), properties[:attrs]
|
17
|
+
else
|
18
|
+
Atom::XML::Node.new properties[:type]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# add_attributes adds the specified attributes to the
|
24
|
+
# given node. It takes a LibXML::XML::Node and an
|
25
|
+
# array of name, value attribute pairs.
|
26
|
+
#
|
27
|
+
# add_attribute node, [['title', 'emperor'], ['name', 'Napoleon']]
|
28
|
+
#
|
29
|
+
# add_attribute returns the modified node.
|
30
|
+
def add_attributes(node, attributes)
|
31
|
+
attributes.each do |attribute|
|
32
|
+
node.attributes[attribute[0]] = attribute[1]
|
33
|
+
end
|
34
|
+
|
35
|
+
node
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns true if "true" and false if "false"
|
39
|
+
|
40
|
+
#
|
41
|
+
# @param [String] value
|
42
|
+
#
|
43
|
+
# @visibility public
|
44
|
+
# @return
|
45
|
+
def check_value(value)
|
46
|
+
case value
|
47
|
+
when 'true'
|
48
|
+
true
|
49
|
+
when 'false'
|
50
|
+
false
|
51
|
+
else
|
52
|
+
value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module GoogleAppsOauth2
|
2
|
+
module Atom
|
3
|
+
class User < Document
|
4
|
+
attr_reader :doc, :login, :suspended, :first_name, :last_name, :quota, :password
|
5
|
+
|
6
|
+
MAP = {
|
7
|
+
userName: :login,
|
8
|
+
suspended: :suspended,
|
9
|
+
familyName: :last_name,
|
10
|
+
givenName: :first_name,
|
11
|
+
limit: :quota,
|
12
|
+
password: :password
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(xml)
|
16
|
+
super(xml, MAP)
|
17
|
+
find_values
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
|
3
|
+
module GoogleAppsOauth2
|
4
|
+
module Parsers
|
5
|
+
class FeedParser < HTTParty::Parser
|
6
|
+
SupportedFormats = {"application/atom+xml" => :atom}
|
7
|
+
|
8
|
+
protected
|
9
|
+
|
10
|
+
def atom
|
11
|
+
GoogleAppsOauth2::Atom::Feed.new(body).items
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'openssl'
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'httparty'
|
5
|
+
|
6
|
+
module GoogleAppsOauth2
|
7
|
+
class Transport
|
8
|
+
include HTTParty
|
9
|
+
|
10
|
+
parser GoogleAppsOauth2::Parsers::FeedParser
|
11
|
+
|
12
|
+
base_uri 'https://apps-apis.google.com/a/feeds'
|
13
|
+
|
14
|
+
def initialize(options, &block)
|
15
|
+
@domain = options[:domain]
|
16
|
+
@token = options[:token]
|
17
|
+
@client_id = options[:client_id]
|
18
|
+
@client_secret = options[:client_secret]
|
19
|
+
@refresh_token = options[:refresh_token]
|
20
|
+
@token_changed_block = block
|
21
|
+
|
22
|
+
@user_path= "/#{@domain}/user/2.0"
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_users(options = {})
|
26
|
+
headers = {'content-type' => 'application/atom+xml', 'Authorization' => "OAuth #{token}"}
|
27
|
+
|
28
|
+
url = user_path + "?startUsername=#{options[:start]}"
|
29
|
+
response = self.class.get(url, headers: headers)
|
30
|
+
response = check_for_refresh(response)
|
31
|
+
|
32
|
+
response
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
attr_reader :user_path
|
37
|
+
attr_reader :domain, :token, :refresh_token, :client_id, :client_secret
|
38
|
+
|
39
|
+
def check_for_refresh(old_response)
|
40
|
+
response = old_response
|
41
|
+
if old_response.code == 401
|
42
|
+
data = {
|
43
|
+
:client_id => @client_id,
|
44
|
+
:client_secret => @client_secret,
|
45
|
+
:refresh_token => refresh_token,
|
46
|
+
:grant_type => "refresh_token"
|
47
|
+
}
|
48
|
+
response_json = MultiJson.load(self.class.post("https://accounts.google.com/o/oauth2/token", :body => data))
|
49
|
+
@token = response_json["access_token"]
|
50
|
+
@token_changed_block.call(@token)
|
51
|
+
headers = old_response.request.options[:headers].merge("Authorization" => "OAuth #{token}")
|
52
|
+
response = self.class.get(old_response.request.uri.to_s, headers: headers)
|
53
|
+
end
|
54
|
+
response
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
{
|
2
|
+
"access_token": "some-new-token",
|
3
|
+
"token_type": "Bearer",
|
4
|
+
"expires_in": 3600,
|
5
|
+
"id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjNmNDUwOTU1YzA3ZGUyZWFhNDA1OTg1NmMzOGFmYWY1OTFmMWUxYTcifQ.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY2lkIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiYXpwIjoiNDA3NDA4NzE4MTkyLmFwcHMuZ29vZ2xldXNlcmNvbnRlbnQuY29tIiwiaGQiOiJsb29wYi5hYyIsInRva2VuX2hhc2giOiI3RnV3YXBnV1dXaWxHc1l6dm9SZURBIiwiYXRfaGFzaCI6IjdGdXdhcGdXV1dpbEdzWXp2b1JlREEiLCJ2ZXJpZmllZF9lbWFpbCI6InRydWUiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6IndpbGxAbG9vcGIuYWMiLCJpZCI6IjEwMjY2MDg3NTAyMjk1NTU4OTU2MCIsInN1YiI6IjEwMjY2MDg3NTAyMjk1NTU4OTU2MCIsImF1ZCI6IjQwNzQwODcxODE5Mi5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSIsImlhdCI6MTM2MTQ5ODU5OSwiZXhwIjoxMzYxNTAyNDk5fQ.HblnwmqqyQoapWIZP90QqB92MBTUQEnYRjvFRO8BaWV2VVQIgc2v1Vb_oOaChNs3w2vXk-2NQiJlWVdqDFARCRe5n3BID2wr8iwSUzVCEt4fg3rqxQ-IeXXWNYCx-32N3k7w4mcnxsnglw7ajol4vkv-2Nx8peA3Zvwt0NXdxj8"
|
6
|
+
}
|
@@ -0,0 +1,5 @@
|
|
1
|
+
<atom:entry xmlns:atom="http://www.w3.org/2005/Atom" xmlns:apps="http://schemas.google.com/apps/2006">
|
2
|
+
<atom:category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/apps/2006#user"/>
|
3
|
+
<apps:login userName="lholcomb2" suspended="false"/>
|
4
|
+
<apps:name familyName="Holcomb" givenName="Lawrence"/>
|
5
|
+
</atom:entry>
|
@@ -0,0 +1,49 @@
|
|
1
|
+
<?xml version='1.0' encoding='UTF-8'?>
|
2
|
+
<feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'
|
3
|
+
xmlns:apps='http://schemas.google.com/apps/2006' xmlns:gd='http://schemas.google.com/g/2005'>
|
4
|
+
<id>https://apps-apis.google.com/a/feeds/loopb.ac/user/2.0</id>
|
5
|
+
<updated>1970-01-01T00:00:00.000Z</updated>
|
6
|
+
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/apps/2006#user'/>
|
7
|
+
<title type='text'>Users</title>
|
8
|
+
<link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml'
|
9
|
+
href='https://apps-apis.google.com/a/feeds/loopb.ac/user/2.0'/>
|
10
|
+
<link rel='http://schemas.google.com/g/2005#post' type='application/atom+xml'
|
11
|
+
href='https://apps-apis.google.com/a/feeds/loopb.ac/user/2.0'/>
|
12
|
+
<link rel='self' type='application/atom+xml' href='https://apps-apis.google.com/a/feeds/loopb.ac/user/2.0'/>
|
13
|
+
<link rel='next' type='application/atom+xml' href='https://apps-apis.google.com/a/feeds/cnm.edu/user/2.0?startUsername=aadams37'/>
|
14
|
+
<openSearch:startIndex>1</openSearch:startIndex>
|
15
|
+
<entry>
|
16
|
+
<id>https://apps-apis.google.com/a/feeds/loopb.ac/user/2.0/misterunfriendly</id>
|
17
|
+
<updated>1970-01-01T00:00:00.000Z</updated>
|
18
|
+
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/apps/2006#user'/>
|
19
|
+
<title type='text'>misterunfriendly</title>
|
20
|
+
<link rel='self' type='application/atom+xml'
|
21
|
+
href='https://apps-apis.google.com/a/feeds/loopb.ac/user/2.0/misterunfriendly'/>
|
22
|
+
<link rel='edit' type='application/atom+xml'
|
23
|
+
href='https://apps-apis.google.com/a/feeds/loopb.ac/user/2.0/misterunfriendly'/>
|
24
|
+
<apps:login userName='misterunfriendly' suspended='false' ipWhitelisted='false' admin='false'
|
25
|
+
changePasswordAtNextLogin='true' agreedToTerms='true'/>
|
26
|
+
<apps:quota limit='25600'/>
|
27
|
+
<apps:name familyName='Unfriendly' givenName='Mister'/>
|
28
|
+
<gd:feedLink rel='http://schemas.google.com/apps/2006#user.nicknames'
|
29
|
+
href='https://apps-apis.google.com/a/feeds/loopb.ac/nickname/2.0?username=misterunfriendly'/>
|
30
|
+
<gd:feedLink rel='http://schemas.google.com/apps/2006#user.emailLists'
|
31
|
+
href='https://apps-apis.google.com/a/feeds/loopb.ac/emailList/2.0?recipient=misterunfriendly%40loopb.ac'/>
|
32
|
+
</entry>
|
33
|
+
<entry>
|
34
|
+
<id>https://apps-apis.google.com/a/feeds/loopb.ac/user/2.0/will</id>
|
35
|
+
<updated>1970-01-01T00:00:00.000Z</updated>
|
36
|
+
<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/apps/2006#user'/>
|
37
|
+
<title type='text'>will</title>
|
38
|
+
<link rel='self' type='application/atom+xml' href='https://apps-apis.google.com/a/feeds/loopb.ac/user/2.0/will'/>
|
39
|
+
<link rel='edit' type='application/atom+xml' href='https://apps-apis.google.com/a/feeds/loopb.ac/user/2.0/will'/>
|
40
|
+
<apps:login userName='will' suspended='false' ipWhitelisted='false' admin='true' changePasswordAtNextLogin='false'
|
41
|
+
agreedToTerms='true'/>
|
42
|
+
<apps:quota limit='25600'/>
|
43
|
+
<apps:name familyName='Read' givenName='Will'/>
|
44
|
+
<gd:feedLink rel='http://schemas.google.com/apps/2006#user.nicknames'
|
45
|
+
href='https://apps-apis.google.com/a/feeds/loopb.ac/nickname/2.0?username=will'/>
|
46
|
+
<gd:feedLink rel='http://schemas.google.com/apps/2006#user.emailLists'
|
47
|
+
href='https://apps-apis.google.com/a/feeds/loopb.ac/emailList/2.0?recipient=will%40loopb.ac'/>
|
48
|
+
</entry>
|
49
|
+
</feed>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoogleAppsOauth2::Atom::Document do
|
4
|
+
let (:document) { GoogleAppsOauth2::Atom::Document.new File.read('spec/fixture_xml/user.xml') }
|
5
|
+
let (:doc_string) { File.read('spec/fixture_xml/users_feed.xml') }
|
6
|
+
|
7
|
+
describe "#parse" do
|
8
|
+
it "parses the given XML document" do
|
9
|
+
document.parse(doc_string).should be_a LibXML::XML::Document
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "#to_s" do
|
14
|
+
it "Outputs @doc as a string" do
|
15
|
+
document.to_s.should be_a String
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|