├── .bundle └── config ├── .gitignore ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── TODO.txt ├── garb.gemspec ├── lib ├── garb.rb └── garb │ ├── core_ext │ ├── array.rb │ ├── string.rb │ └── symbol.rb │ ├── destination.rb │ ├── filter_parameters.rb │ ├── management │ ├── account.rb │ ├── feed.rb │ ├── goal.rb │ ├── profile.rb │ ├── segment.rb │ └── web_property.rb │ ├── model.rb │ ├── profile_reports.rb │ ├── report_parameter.rb │ ├── report_response.rb │ ├── reports.rb │ ├── reports │ ├── bounces.rb │ ├── exits.rb │ ├── pageviews.rb │ ├── unique_pageviews.rb │ └── visits.rb │ ├── request │ ├── authentication.rb │ └── data.rb │ ├── result_set.rb │ ├── session.rb │ ├── step.rb │ ├── support.rb │ └── version.rb └── test ├── fixtures ├── cacert.pem ├── profile_feed.json ├── profile_feed.xml ├── report_feed.json └── report_feed.xml ├── test_helper.rb └── unit ├── garb ├── filter_parameters_test.rb ├── management │ ├── account_test.rb │ ├── feed_test.rb │ ├── goal_test.rb │ ├── profile_test.rb │ ├── segment_test.rb │ └── web_property_test.rb ├── model_test.rb ├── oauth_session_test.rb ├── profile_reports_test.rb ├── report_parameter_test.rb ├── report_response_test.rb ├── request │ ├── authentication_test.rb │ └── data_test.rb └── session_test.rb ├── garb_test.rb └── symbol_operator_test.rb /.bundle/config: -------------------------------------------------------------------------------- 1 | --- {} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /pkg/ 2 | demo.rb 3 | .DS_Store 4 | output.xml 5 | coverage 6 | .rvmrc 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Version 0.9.2 2 | 3 | * Removed all deprecated features: Garb::Report, Garb::Resource, Garb::Profile, and Garb::Account 4 | * Moved the differing types of requests into a module, will refactor to share more code 5 | * Fixed OR'ing in :filters option for results 6 | 7 | Version 0.9.0 8 | 9 | * New Garb::Model is solid. Garb::Resource and Garb::Report are deprecated. 10 | * New GA Management API is functional and should be used in place of Garb::Profile and Garb::Account 11 | 12 | Version 0.7.4 13 | 14 | * Removes HappyMapper dependency in favor of Crack so we can drop libxml dependency 15 | 16 | Version 0.7.0 17 | 18 | * Adds multi-session support 19 | 20 | Version 0.6.0 21 | 22 | * Adds OAuth access token support 23 | * Simplifies Garb report access through a profile 24 | * Includes the start of a basic library of pre-built reports (require 'garb/reports') 25 | 26 | Version 0.5.1 27 | 28 | * Brings back hash filters and symbol operators after agreed upon SymbolOperator 29 | 30 | Version 0.5.0 31 | 32 | * Filters now have a new DSL so that I could toss Symbol operators (which conflict with DataMapper and MongoMapper) 33 | * The method of passing a hash to filters no longer works, at all 34 | 35 | Version 0.4.0 36 | 37 | * Changes the api for filters and sort making it consistent with metrics/dimensions 38 | * If you wish to clear the defaults defined on a class, you may use clear_(filters/sort/metrics/dimensions) 39 | * To make a custom class using Garb::Resource, you must now extend instead of include the module 40 | 41 | Version 0.3.2 42 | 43 | * adds Profile.first which can be used to get the first profile with a table id, or web property id (UA number) 44 | 45 | Version 0.2.4 46 | 47 | * requires happymapper from rubygems, version 0.2.5. Be sure to update. 48 | 49 | Version 0.2.0 50 | 51 | * makes major changes (compared to 0.1.0) to the way garb is used to build reports. 52 | * There is now both a module that gets included for generating defined classes, 53 | * slight changes to the way that the Report class can be used. 54 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | source "http://gems.github.com" 3 | 4 | gem 'rake', '~> 0.8.7' 5 | 6 | gem 'i18n' 7 | 8 | group :test do 9 | gem 'shoulda' 10 | gem 'simplecov' 11 | gem 'minitest', :require => false 12 | gem 'jferris-mocha', :require => false 13 | gem 'yajl-ruby' 14 | end 15 | 16 | group :demo do 17 | gem 'oauth' 18 | end 19 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | remote: http://gems.github.com/ 4 | specs: 5 | i18n (0.4.2) 6 | jferris-mocha (0.9.8.20100526112143) 7 | rake 8 | minitest (2.0.0) 9 | oauth (0.4.4) 10 | rake (0.8.7) 11 | shoulda (2.11.3) 12 | simplecov (0.3.7) 13 | simplecov-html (>= 0.3.7) 14 | simplecov-html (0.3.9) 15 | yajl-ruby (0.8.2) 16 | 17 | PLATFORMS 18 | ruby 19 | 20 | DEPENDENCIES 21 | i18n 22 | jferris-mocha 23 | minitest 24 | oauth 25 | rake (~> 0.8.7) 26 | shoulda 27 | simplecov 28 | yajl-ruby 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Garb 2 | ==== 3 | 4 | The official repository is now located here: https://github.com/Sija/garb 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rake/gempackagetask' 2 | require 'rake/testtask' 3 | 4 | $:.unshift File.expand_path('../lib', __FILE__) 5 | require 'garb' 6 | 7 | task :default => :test 8 | 9 | spec = Gem::Specification.new do |s| 10 | s.name = 'garb' 11 | s.version = Garb::Version.to_s 12 | s.has_rdoc = false 13 | s.rubyforge_project = 'viget' 14 | s.summary = "Google Analytics API Ruby Wrapper" 15 | s.authors = ['Tony Pitale'] 16 | s.email = 'tony.pitale@viget.com' 17 | s.homepage = 'http://github.com/vigetlabs/garb' 18 | s.files = %w(README.md CHANGELOG.md Rakefile) + Dir.glob("lib/**/*") 19 | s.test_files = Dir.glob("test/**/*") 20 | 21 | s.add_dependency("crack", [">= 0.1.6"]) 22 | s.add_dependency("activesupport", [">= 2.2.0"]) 23 | end 24 | 25 | Rake::GemPackageTask.new(spec) do |pkg| 26 | pkg.gem_spec = spec 27 | end 28 | 29 | Rake::TestTask.new do |t| 30 | t.libs << 'test' 31 | t.test_files = FileList["test/**/*_test.rb"] 32 | t.verbose = true 33 | end 34 | 35 | desc 'Generate the gemspec to serve this Gem from Github' 36 | task :github do 37 | file = File.dirname(__FILE__) + "/#{spec.name}.gemspec" 38 | File.open(file, 'w') {|f| f << spec.to_ruby } 39 | puts "Created gemspec: #{file}" 40 | end 41 | 42 | begin 43 | require 'rcov/rcovtask' 44 | 45 | desc "Generate RCov coverage report" 46 | Rcov::RcovTask.new(:rcov) do |t| 47 | t.libs << "test" 48 | t.test_files = FileList['test/**/*_test.rb'] 49 | t.rcov_opts << "-x \"test/*,gems/*,/Library/Ruby/*,config/*\" -x lib/garb.rb -x lib/garb/version.rb --rails" 50 | end 51 | rescue LoadError 52 | nil 53 | end 54 | 55 | task :default => 'test' 56 | 57 | # EOF 58 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | TODO 2 | ==== 3 | -------------------------------------------------------------------------------- /garb.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |s| 4 | s.name = %q{garb} 5 | s.version = "0.9.2" 6 | 7 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 8 | s.authors = ["Tony Pitale"] 9 | s.date = %q{2011-06-30} 10 | s.email = %q{tony.pitale@viget.com} 11 | s.files = ["README.md", "CHANGELOG.md", "Rakefile", "lib/garb", "lib/garb/destination.rb", "lib/garb/filter_parameters.rb", "lib/garb/management", "lib/garb/management/account.rb", "lib/garb/management/feed.rb", "lib/garb/management/goal.rb", "lib/garb/management/profile.rb", "lib/garb/management/segment.rb", "lib/garb/management/web_property.rb", "lib/garb/model.rb", "lib/garb/profile_reports.rb", "lib/garb/report_parameter.rb", "lib/garb/report_response.rb", "lib/garb/reports", "lib/garb/reports/bounces.rb", "lib/garb/reports/exits.rb", "lib/garb/reports/pageviews.rb", "lib/garb/reports/unique_pageviews.rb", "lib/garb/reports/visits.rb", "lib/garb/reports.rb", "lib/garb/request", "lib/garb/request/authentication.rb", "lib/garb/request/data.rb", "lib/garb/result_set.rb", "lib/garb/session.rb", "lib/garb/step.rb", "lib/garb/version.rb", "lib/garb.rb", "lib/support.rb", "test/fixtures", "test/fixtures/cacert.pem", "test/fixtures/profile_feed.xml", "test/fixtures/report_feed.xml", "test/test_helper.rb", "test/unit", "test/unit/garb", "test/unit/garb/filter_parameters_test.rb", "test/unit/garb/management", "test/unit/garb/management/account_test.rb", "test/unit/garb/management/feed_test.rb", "test/unit/garb/management/goal_test.rb", "test/unit/garb/management/profile_test.rb", "test/unit/garb/management/segment_test.rb", "test/unit/garb/management/web_property_test.rb", "test/unit/garb/model_test.rb", "test/unit/garb/oauth_session_test.rb", "test/unit/garb/profile_reports_test.rb", "test/unit/garb/report_parameter_test.rb", "test/unit/garb/report_response_test.rb", "test/unit/garb/request", "test/unit/garb/request/authentication_test.rb", "test/unit/garb/request/data_test.rb", "test/unit/garb/session_test.rb", "test/unit/garb_test.rb", "test/unit/symbol_operator_test.rb"] 12 | s.homepage = %q{http://github.com/vigetlabs/garb} 13 | s.require_paths = ["lib"] 14 | s.rubyforge_project = %q{viget} 15 | s.rubygems_version = %q{1.6.2} 16 | s.summary = %q{Google Analytics API Ruby Wrapper} 17 | s.test_files = ["test/fixtures", "test/fixtures/cacert.pem", "test/fixtures/profile_feed.xml", "test/fixtures/report_feed.xml", "test/test_helper.rb", "test/unit", "test/unit/garb", "test/unit/garb/filter_parameters_test.rb", "test/unit/garb/management", "test/unit/garb/management/account_test.rb", "test/unit/garb/management/feed_test.rb", "test/unit/garb/management/goal_test.rb", "test/unit/garb/management/profile_test.rb", "test/unit/garb/management/segment_test.rb", "test/unit/garb/management/web_property_test.rb", "test/unit/garb/model_test.rb", "test/unit/garb/oauth_session_test.rb", "test/unit/garb/profile_reports_test.rb", "test/unit/garb/report_parameter_test.rb", "test/unit/garb/report_response_test.rb", "test/unit/garb/request", "test/unit/garb/request/authentication_test.rb", "test/unit/garb/request/data_test.rb", "test/unit/garb/session_test.rb", "test/unit/garb_test.rb", "test/unit/symbol_operator_test.rb"] 18 | 19 | if s.respond_to? :specification_version then 20 | s.specification_version = 3 21 | 22 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 23 | s.add_runtime_dependency(%q, [">= 0.1.6"]) 24 | s.add_runtime_dependency(%q, [">= 2.2.0"]) 25 | else 26 | s.add_dependency(%q, [">= 0.1.6"]) 27 | s.add_dependency(%q, [">= 2.2.0"]) 28 | end 29 | else 30 | s.add_dependency(%q, [">= 0.1.6"]) 31 | s.add_dependency(%q, [">= 2.2.0"]) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/garb.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'net/https' 3 | 4 | require 'cgi' 5 | require 'ostruct' 6 | 7 | begin 8 | require 'yajl/json_gem' # JSON.parse 9 | rescue LoadError 10 | require 'json' 11 | end 12 | 13 | module Garb 14 | autoload :Destination, 'garb/destination' 15 | autoload :FilterParameters, 'garb/filter_parameters' 16 | autoload :Model, 'garb/model' 17 | autoload :ProfileReports, 'garb/profile_reports' 18 | autoload :ReportParameter, 'garb/report_parameter' 19 | autoload :ReportResponse, 'garb/report_response' 20 | autoload :ResultSet, 'garb/result_set' 21 | autoload :Session, 'garb/session' 22 | autoload :Step, 'garb/step' 23 | autoload :Version, 'garb/version' 24 | 25 | module Management 26 | autoload :Account, 'garb/management/account' 27 | autoload :Feed, 'garb/management/feed' 28 | autoload :Goal, 'garb/management/goal' 29 | autoload :Profile, 'garb/management/profile' 30 | autoload :Segment, 'garb/management/segment' 31 | autoload :WebProperty, 'garb/management/web_property' 32 | end 33 | 34 | module Request 35 | autoload :Authentication, "garb/request/authentication" 36 | autoload :Data, 'garb/request/data' 37 | end 38 | end 39 | 40 | # require 'garb/account_feed_request' 41 | # require 'garb/resource' 42 | # require 'garb/report' 43 | 44 | module Garb 45 | GA = "http://schemas.google.com/analytics/2008" 46 | 47 | extend self 48 | 49 | class << self 50 | attr_accessor :proxy_address, :proxy_port, :proxy_user, :proxy_password 51 | attr_writer :read_timeout 52 | end 53 | 54 | def read_timeout 55 | @read_timeout || 60 56 | end 57 | 58 | def to_google_analytics(thing) 59 | return thing.to_google_analytics if thing.respond_to?(:to_google_analytics) 60 | 61 | "#{$1}ga:#{$2}" if "#{thing.to_s.camelize(:lower)}" =~ /^(-)?(.*)$/ 62 | end 63 | alias :to_ga :to_google_analytics 64 | 65 | def from_google_analytics(thing) 66 | thing.to_s.gsub(/^ga\:/, '').underscore 67 | end 68 | alias :from_ga :from_google_analytics 69 | 70 | def parse_properties(entry) 71 | Hash[entry['dxp$property'].map {|p| [Garb.from_ga(p['name']),p['value']]}] 72 | end 73 | 74 | def parse_link(entry, rel) 75 | entry['link'].detect {|link| link["rel"] == rel}['href'] 76 | end 77 | 78 | def symbol_operator_slugs 79 | [:eql, :not_eql, :gt, :gte, :lt, :lte, :desc, :descending, :matches, 80 | :does_not_match, :contains, :does_not_contain, :substring, :not_substring] 81 | end 82 | 83 | # new(address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil) 84 | 85 | # opts => open_timeout, read_timeout, ssl_timeout 86 | # probably just support open_timeout 87 | end 88 | 89 | require 'garb/support' 90 | -------------------------------------------------------------------------------- /lib/garb/core_ext/array.rb: -------------------------------------------------------------------------------- 1 | unless Object.const_defined?("ActiveSupport") 2 | class Array 3 | def self.wrap(object) 4 | if object.nil? 5 | [] 6 | elsif object.respond_to?(:to_ary) 7 | object.to_ary 8 | else 9 | [object] 10 | end 11 | end 12 | end 13 | else 14 | puts "ActiveSupport is suddenly defined!" 15 | end 16 | -------------------------------------------------------------------------------- /lib/garb/core_ext/string.rb: -------------------------------------------------------------------------------- 1 | # Pull in some AS String utilities (not loaded if AS is available) 2 | unless Object.const_defined?("ActiveSupport") 3 | class String 4 | def camelize(first_letter = :upper) 5 | case first_letter 6 | when :upper then Garb::Inflector.camelize(self, true) 7 | when :lower then Garb::Inflector.camelize(self, false) 8 | end 9 | end 10 | alias_method :camelcase, :camelize 11 | 12 | def underscore 13 | Garb::Inflector.underscore(self) 14 | end 15 | 16 | def demodulize 17 | Garb::Inflector.demodulize(self) 18 | end 19 | end 20 | 21 | 22 | module Garb 23 | module Inflector 24 | extend self 25 | 26 | def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) 27 | if first_letter_in_uppercase 28 | lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } 29 | else 30 | lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1] 31 | end 32 | end 33 | 34 | def underscore(camel_cased_word) 35 | word = camel_cased_word.to_s.dup 36 | word.gsub!(/::/, '/') 37 | word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') 38 | word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') 39 | word.tr!("-", "_") 40 | word.downcase! 41 | word 42 | end 43 | 44 | def demodulize(class_name_in_module) 45 | class_name_in_module.to_s.gsub(/^.*::/, '') 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/garb/core_ext/symbol.rb: -------------------------------------------------------------------------------- 1 | module SymbolOperatorMethods 2 | def to_google_analytics 3 | operators = { 4 | :eql => '==', 5 | :not_eql => '!=', 6 | :gt => '>', 7 | :gte => '>=', 8 | :lt => '<', 9 | :lte => '<=', 10 | :matches => '==', 11 | :does_not_match => '!=', 12 | :contains => '=~', 13 | :does_not_contain => '!~', 14 | :substring => '=@', 15 | :not_substring => '!@', 16 | :desc => '-', 17 | :descending => '-' 18 | } 19 | 20 | t = Garb.to_google_analytics(@field || @target) 21 | o = operators[@operator] 22 | 23 | [:desc, :descending].include?(@operator) ? "#{o}#{t}" : "#{t}#{o}" 24 | end 25 | end 26 | 27 | class SymbolOperator 28 | def initialize(field, operator) 29 | @field, @operator = field, operator 30 | end unless method_defined?(:initialize) 31 | 32 | include SymbolOperatorMethods 33 | end 34 | 35 | symbol_slugs = [] 36 | 37 | if Object.const_defined?("DataMapper") 38 | # make sure the class is defined 39 | require 'dm-core/core_ext/symbol' 40 | 41 | # add to_google_analytics to DM's Opeartor 42 | class DataMapper::Query::Operator 43 | include SymbolOperatorMethods 44 | end 45 | 46 | symbol_slugs = (Garb.symbol_operator_slugs - DataMapper::Query::Conditions::Comparison.slugs) 47 | else 48 | symbol_slugs = Garb.symbol_operator_slugs 49 | end 50 | 51 | # define the remaining symbol operators 52 | symbol_slugs.each do |operator| 53 | Symbol.class_eval <<-RUBY 54 | def #{operator} 55 | warn("The use of SymbolOperator(#{operator}, etc.) has been deprecated. Please use named filters.") 56 | SymbolOperator.new(self, :#{operator}) 57 | end unless method_defined?(:#{operator}) 58 | RUBY 59 | end 60 | -------------------------------------------------------------------------------- /lib/garb/destination.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | class Destination 3 | attr_reader :match_type, :expression, :steps, :case_sensitive 4 | 5 | alias :case_sensitive? :case_sensitive 6 | 7 | def initialize(attributes) 8 | return unless attributes.is_a?(Hash) 9 | 10 | @match_type = attributes['matchType'] 11 | @expression = attributes['expression'] 12 | @case_sensitive = (attributes['caseSensitive'] == 'true') 13 | 14 | step_attributes = attributes[Garb.to_ga('step')] 15 | @steps = Array(step_attributes.is_a?(Hash) ? [step_attributes] : step_attributes).map {|s| Step.new(s)} 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/garb/filter_parameters.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | class FilterParameters 3 | # def self.define_operators(*methods) 4 | # methods.each do |method| 5 | # class_eval <<-CODE 6 | # def #{method}(field, value) 7 | # @filter_hash.merge!({SymbolOperator.new(field, :#{method}) => value}) 8 | # end 9 | # CODE 10 | # end 11 | # end 12 | 13 | # define_operators :eql, :not_eql, :gt, :gte, :lt, :lte, :matches, 14 | # :does_not_match, :contains, :does_not_contain, :substring, :not_substring 15 | 16 | attr_accessor :parameters 17 | 18 | def initialize(parameters) 19 | self.parameters = (Array.wrap(parameters) || []).compact 20 | end 21 | 22 | [{}, [{}, {}]] 23 | def to_params 24 | value = array_to_params(self.parameters) 25 | 26 | value.empty? ? {} : {'filters' => value} 27 | end 28 | 29 | private 30 | def array_to_params(arr) 31 | arr.map do |param| 32 | param.is_a?(Hash) ? hash_to_params(param) : array_to_params(param) 33 | end.join(',') # Array OR 34 | end 35 | 36 | def hash_to_params(hsh) 37 | hsh.map do |k,v| 38 | next unless k.is_a?(SymbolOperatorMethods) 39 | 40 | escaped_v = v.to_s.gsub(/([,;\\])/) {|c| '\\'+c} 41 | "#{URI.encode(k.to_google_analytics, /[=<>]/)}#{CGI::escape(escaped_v)}" 42 | end.join('%3B') # Hash AND (no duplicate keys), escape char for ';' fixes oauth 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/garb/management/account.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | module Management 3 | class Account 4 | attr_accessor :session, :path 5 | attr_accessor :id, :title, :name 6 | 7 | def self.all(session = Session) 8 | feed = Feed.new(session, '/accounts') 9 | feed.entries.map {|entry| new_from_entry(entry, session)} 10 | end 11 | 12 | def self.new_from_entry(entry, session) 13 | account = new 14 | account.session = session 15 | account.path = Garb.parse_link(entry, "self").gsub(Feed::BASE_URL, '') 16 | account.title = entry['title'].gsub('Google Analytics Account ', '') # can we get this in properties=? 17 | account.properties = Garb.parse_properties(entry) 18 | account 19 | end 20 | 21 | def properties=(properties) 22 | self.id = properties["account_id"] 23 | self.name = properties["account_name"] 24 | end 25 | 26 | def web_properties 27 | @web_properties ||= WebProperty.for_account(self) 28 | end 29 | 30 | def profiles 31 | @profiles ||= Profile.for_account(self) 32 | end 33 | 34 | def goals 35 | @goals ||= Goal.for_account(self) 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/garb/management/feed.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | module Management 3 | class Feed 4 | BASE_URL = "https://www.google.com/analytics/feeds/datasources/ga" 5 | 6 | attr_reader :request 7 | 8 | def initialize(session, path) 9 | @request = Request::Data.new(session, BASE_URL+path) 10 | end 11 | 12 | def parsed_response 13 | @parsed_response ||= JSON.parse(response.body) 14 | end 15 | 16 | def entries 17 | # possible to have nil entries, yuck 18 | parsed_response ? [parsed_response['feed']['entry']].flatten.compact : [] 19 | end 20 | 21 | def response 22 | @response ||= request.send_request 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/garb/management/goal.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | module Management 3 | class Goal 4 | 5 | attr_accessor :session, :path 6 | attr_accessor :name, :number, :value, :destination, :active 7 | 8 | alias :active? :active 9 | 10 | def self.all(session = Session, path = '/accounts/~all/webproperties/~all/profiles/~all/goals') 11 | feed = Feed.new(session, path) 12 | feed.entries.map {|entry| new_from_entry(entry, session)} 13 | end 14 | 15 | def self.for_account(account) 16 | all(account.session, account.path + '/webproperties/~all/profiles/~all/goals') 17 | end 18 | 19 | def self.for_web_property(web_property) 20 | all(web_property.session, web_property.path + '/profiles/~all/goals') 21 | end 22 | 23 | def self.for_profile(profile) 24 | all(profile.session, profile.path + '/goals') 25 | end 26 | 27 | def self.new_from_entry(entry, session) 28 | goal = new 29 | goal.session = session 30 | goal.path = Garb.parse_link(entry, "self").gsub(Feed::BASE_URL, '') 31 | goal.properties = entry[Garb.to_ga('goal')] 32 | goal 33 | end 34 | 35 | def properties=(properties) 36 | self.name = properties["name"] 37 | self.number = properties["number"].to_i 38 | self.value = properties["value"].to_f 39 | self.active = (properties["active"] == "true") 40 | self.destination = Destination.new(properties[Garb.to_ga('destination')]) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/garb/management/profile.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | module Management 3 | class Profile 4 | 5 | include ProfileReports 6 | 7 | attr_accessor :session, :path 8 | attr_accessor :id, :table_id, :title, :account_id, :web_property_id 9 | 10 | def self.all(session = Session, path = '/accounts/~all/webproperties/~all/profiles') 11 | feed = Feed.new(session, path) 12 | feed.entries.map {|entry| new_from_entry(entry, session)} 13 | end 14 | 15 | def self.for_account(account) 16 | all(account.session, account.path+'/webproperties/~all/profiles') 17 | end 18 | 19 | def self.for_web_property(web_property) 20 | all(web_property.session, web_property.path+'/profiles') 21 | end 22 | 23 | def self.new_from_entry(entry, session) 24 | profile = new 25 | profile.session = session 26 | profile.path = Garb.parse_link(entry, "self").gsub(Feed::BASE_URL, '') 27 | profile.properties = Garb.parse_properties(entry) 28 | profile 29 | end 30 | 31 | def properties=(properties) 32 | self.id = properties['profile_id'] 33 | self.table_id = properties['dxp:table_id'] 34 | self.title = properties['profile_name'] 35 | self.account_id = properties['account_id'] 36 | self.web_property_id = properties['web_property_id'] 37 | end 38 | 39 | def goals 40 | Goal.for_profile(self) 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/garb/management/segment.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | module Management 3 | class Segment 4 | attr_accessor :session, :path 5 | attr_accessor :id, :name, :definition 6 | 7 | def self.all(session = Session) 8 | feed = Feed.new(session, '/segments') 9 | feed.entries.map {|entry| new_from_entry(entry, session)} 10 | end 11 | 12 | def self.new_from_entry(entry, session) 13 | segment = new 14 | segment.session = session 15 | segment.path = Garb.parse_link(entry, "self").gsub(Feed::BASE_URL, '') 16 | segment.properties = entry['dxp:segment'] 17 | segment 18 | end 19 | 20 | def properties=(properties) 21 | self.id = properties['id'] 22 | self.name = properties['name'] 23 | self.definition = properties['dxp:definition'] 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/garb/management/web_property.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | module Management 3 | class WebProperty 4 | attr_accessor :session, :path 5 | attr_accessor :id, :account_id 6 | 7 | def self.all(session = Session, path='/accounts/~all/webproperties') 8 | feed = Feed.new(session, path) 9 | feed.entries.map {|entry| new_from_entry(entry, session)} 10 | end 11 | 12 | def self.for_account(account) 13 | all(account.session, account.path+'/webproperties') 14 | end 15 | 16 | def self.new_from_entry(entry, session) 17 | web_property = new 18 | web_property.session = session 19 | web_property.path = Garb.parse_link(entry, "self").gsub(Feed::BASE_URL, '') 20 | web_property.properties = Garb.parse_properties(entry) 21 | web_property 22 | end 23 | 24 | def properties=(properties) 25 | self.id = properties["web_property_id"] 26 | self.account_id = properties["account_id"] 27 | end 28 | 29 | def profiles 30 | @profiles ||= Profile.for_web_property(self) 31 | end 32 | 33 | def goals 34 | @goals ||= Goal.for_web_property(self) 35 | end 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/garb/model.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | module Model 3 | MONTH = 2592000 4 | URL = "https://www.google.com/analytics/feeds/data" 5 | 6 | def self.extended(base) 7 | ProfileReports.add_report_method(base) 8 | end 9 | 10 | def metrics(*fields) 11 | @metrics ||= ReportParameter.new(:metrics) 12 | @metrics << fields 13 | end 14 | 15 | def dimensions(*fields) 16 | @dimensions ||= ReportParameter.new(:dimensions) 17 | @dimensions << fields 18 | end 19 | 20 | def set_instance_klass(klass) 21 | @instance_klass = klass 22 | end 23 | 24 | def instance_klass 25 | @instance_klass || OpenStruct 26 | end 27 | 28 | def results(profile, options = {}) 29 | start_date = options.fetch(:start_date, Time.now - MONTH) 30 | end_date = options.fetch(:end_date, Time.now) 31 | default_params = build_default_params(profile, start_date, end_date) 32 | 33 | param_set = [ 34 | default_params, 35 | metrics.to_params, 36 | dimensions.to_params, 37 | parse_filters(options).to_params, 38 | parse_segment(options), 39 | parse_sort(options).to_params, 40 | build_page_params(options) 41 | ] 42 | 43 | data = send_request_for_data(profile, build_params(param_set)) 44 | ReportResponse.new(data, instance_klass).results 45 | end 46 | 47 | private 48 | def send_request_for_data(profile, params) 49 | request = Request::Data.new(profile.session, URL, params) 50 | response = request.send_request 51 | response.body 52 | end 53 | 54 | def build_params(param_set) 55 | param_set.inject({}) {|p,i| p.merge(i)}.reject{|k,v| v.nil?} 56 | end 57 | 58 | def parse_filters(options) 59 | FilterParameters.new(options[:filters]) 60 | end 61 | 62 | def parse_segment(options) 63 | segment_id = "gaid::#{options[:segment_id].to_i}" if options.has_key?(:segment_id) 64 | {'segment' => segment_id} 65 | end 66 | 67 | def parse_sort(options) 68 | sort = ReportParameter.new(:sort) 69 | sort << options[:sort] if options.has_key?(:sort) 70 | sort 71 | end 72 | 73 | def build_default_params(profile, start_date, end_date) 74 | { 75 | 'ids' => Garb.to_ga(profile.id), 76 | 'start-date' => format_time(start_date), 77 | 'end-date' => format_time(end_date) 78 | } 79 | end 80 | 81 | def build_page_params(options) 82 | {'max-results' => options[:limit], 'start-index' => options[:offset]} 83 | end 84 | 85 | def format_time(t) 86 | t.strftime('%Y-%m-%d') 87 | end 88 | end 89 | end -------------------------------------------------------------------------------- /lib/garb/profile_reports.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | module ProfileReports 3 | def self.add_report_method(klass) 4 | # demodulize leaves potential to redefine 5 | # these methods given different namespaces 6 | method_name = klass.name.to_s.demodulize.underscore 7 | return unless method_name.length > 0 8 | 9 | class_eval <<-CODE 10 | def #{method_name}(opts = {}, &block) 11 | #{klass}.results(self, opts, &block) 12 | end 13 | CODE 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/garb/report_parameter.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | class ReportParameter 3 | 4 | attr_reader :elements 5 | 6 | def initialize(name) 7 | @name = name 8 | @elements = [] 9 | end 10 | 11 | def name 12 | @name.to_s 13 | end 14 | 15 | def <<(element) 16 | (@elements += [element].flatten).compact! 17 | self 18 | end 19 | 20 | def to_params 21 | value = self.elements.map{|param| Garb.to_google_analytics(param)}.join(',') 22 | value.empty? ? {} : {self.name => value} 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/garb/report_response.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | class ReportResponse 3 | KEYS = ['dxp$metric', 'dxp$dimension'] 4 | 5 | def initialize(response_body, instance_klass = OpenStruct) 6 | @data = response_body 7 | @instance_klass = instance_klass 8 | end 9 | 10 | def results 11 | if @results.nil? 12 | @results = ResultSet.new(parse) 13 | @results.total_results = parse_total_results 14 | @results.sampled = parse_sampled_flag 15 | end 16 | 17 | @results 18 | end 19 | 20 | def sampled? 21 | end 22 | 23 | private 24 | def parse 25 | entries.map do |entry| 26 | @instance_klass.new(Hash[ 27 | values_for(entry).map {|v| [Garb.from_ga(v['name']), v['value']]} 28 | ]) 29 | end 30 | end 31 | 32 | def entries 33 | feed? ? [parsed_data['feed']['entry']].flatten.compact : [] 34 | end 35 | 36 | def parse_total_results 37 | feed? ? parsed_data['feed']['openSearch:totalResults'].to_i : 0 38 | end 39 | 40 | def parse_sampled_flag 41 | feed? ? (parsed_data['feed']['dxp$containsSampledData'] == 'true') : false 42 | end 43 | 44 | def parsed_data 45 | @parsed_data ||= JSON.parse(@data) 46 | end 47 | 48 | def feed? 49 | !parsed_data['feed'].nil? 50 | end 51 | 52 | def values_for(entry) 53 | KEYS.map {|k| entry[k]}.flatten.compact 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/garb/reports.rb: -------------------------------------------------------------------------------- 1 | require 'garb/reports/exits' 2 | require 'garb/reports/visits' 3 | require 'garb/reports/bounces' 4 | require 'garb/reports/pageviews' 5 | require 'garb/reports/unique_pageviews' 6 | -------------------------------------------------------------------------------- /lib/garb/reports/bounces.rb: -------------------------------------------------------------------------------- 1 | class Bounces 2 | extend Garb::Resource 3 | 4 | metrics :bounces 5 | end 6 | -------------------------------------------------------------------------------- /lib/garb/reports/exits.rb: -------------------------------------------------------------------------------- 1 | class Exits 2 | extend Garb::Resource 3 | 4 | metrics :exits 5 | end 6 | -------------------------------------------------------------------------------- /lib/garb/reports/pageviews.rb: -------------------------------------------------------------------------------- 1 | class Pageviews 2 | extend Garb::Resource 3 | 4 | metrics :pageviews 5 | end 6 | -------------------------------------------------------------------------------- /lib/garb/reports/unique_pageviews.rb: -------------------------------------------------------------------------------- 1 | class UniquePageviews 2 | extend Garb::Resource 3 | 4 | metrics :unique_pageviews 5 | end 6 | -------------------------------------------------------------------------------- /lib/garb/reports/visits.rb: -------------------------------------------------------------------------------- 1 | class Visits 2 | extend Garb::Resource 3 | 4 | metrics :visits 5 | end -------------------------------------------------------------------------------- /lib/garb/request/authentication.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | module Request 3 | class Authentication 4 | class AuthError < StandardError;end 5 | 6 | URL = 'https://www.google.com/accounts/ClientLogin' 7 | 8 | def initialize(email, password, opts={}) 9 | @email = email 10 | @password = password 11 | @account_type = opts.fetch(:account_type, 'HOSTED_OR_GOOGLE') 12 | end 13 | 14 | def parameters 15 | { 16 | 'Email' => @email, 17 | 'Passwd' => @password, 18 | 'accountType' => @account_type, 19 | 'service' => 'analytics', 20 | 'source' => 'vigetLabs-garb-001' 21 | } 22 | end 23 | 24 | def uri 25 | URI.parse(URL) 26 | end 27 | 28 | def send_request(ssl_mode) 29 | http = Net::HTTP.new(uri.host, uri.port, Garb.proxy_address, Garb.proxy_port) 30 | http.read_timeout = Garb.read_timeout 31 | http.use_ssl = true 32 | http.verify_mode = ssl_mode 33 | 34 | if ssl_mode == OpenSSL::SSL::VERIFY_PEER 35 | http.ca_file = CA_CERT_FILE 36 | end 37 | 38 | http.request(build_request) do |response| 39 | raise AuthError unless response.is_a?(Net::HTTPOK) 40 | end 41 | end 42 | 43 | def build_request 44 | post = Net::HTTP::Post.new(uri.path) 45 | post.set_form_data(parameters) 46 | post 47 | end 48 | 49 | def auth_token(opts={}) 50 | ssl_mode = opts[:secure] ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE 51 | send_request(ssl_mode).body.match(/^Auth=(.*)$/)[1] 52 | end 53 | 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/garb/request/data.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | module Request 3 | class Data 4 | class ClientError < StandardError; end 5 | 6 | attr_writer :format 7 | 8 | def initialize(session, base_url, parameters={}) 9 | @session = session 10 | @base_url = base_url 11 | @parameters = parameters 12 | end 13 | 14 | def parameters 15 | @parameters ||= {} 16 | end 17 | 18 | def query_string 19 | parameters.merge!("alt" => format) 20 | parameter_list = @parameters.map {|k,v| "#{k}=#{v}" } 21 | parameter_list.empty? ? '' : "?#{parameter_list.join('&')}" 22 | end 23 | 24 | def format 25 | @format ||= "json" # TODO Support other formats? 26 | end 27 | 28 | def uri 29 | URI.parse(@base_url) 30 | end 31 | 32 | def send_request 33 | response = if @session.single_user? 34 | single_user_request 35 | elsif @session.oauth_user? 36 | oauth_user_request 37 | end 38 | 39 | raise ClientError, response.body.inspect unless response.kind_of?(Net::HTTPSuccess) 40 | response 41 | end 42 | 43 | def single_user_request 44 | http = Net::HTTP.new(uri.host, uri.port, Garb.proxy_address, Garb.proxy_port) 45 | http.use_ssl = true 46 | http.verify_mode = OpenSSL::SSL::VERIFY_NONE 47 | http.get("#{uri.path}#{query_string}", {'Authorization' => "GoogleLogin auth=#{@session.auth_token}", 'GData-Version' => '2'}) 48 | end 49 | 50 | def oauth_user_request 51 | @session.access_token.get("#{uri}#{query_string}", {'GData-Version' => '2'}) 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/garb/result_set.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | class ResultSet 3 | include Enumerable 4 | 5 | attr_accessor :total_results, :sampled 6 | 7 | alias :sampled? :sampled 8 | 9 | def initialize(results) 10 | @results = results 11 | end 12 | 13 | def each(&block) 14 | @results.each(&block) 15 | end 16 | 17 | def to_a 18 | @results 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/garb/session.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | class Session 3 | module Methods 4 | attr_accessor :auth_token, :access_token, :email 5 | 6 | # use only for single user authentication 7 | def login(email, password, opts={}) 8 | self.email = email 9 | auth_request = Request::Authentication.new(email, password, opts) 10 | self.auth_token = auth_request.auth_token(opts) 11 | end 12 | 13 | def single_user? 14 | auth_token && auth_token.is_a?(String) 15 | end 16 | 17 | def oauth_user? 18 | !access_token.nil? 19 | end 20 | end 21 | 22 | include Methods 23 | extend Methods 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/garb/step.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | class Step 3 | attr_reader :name, :number, :path 4 | 5 | def initialize(attributes) 6 | return unless attributes.is_a?(Hash) 7 | 8 | @name = attributes['name'] 9 | @number = attributes['number'].to_i 10 | @path = attributes['path'] 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/garb/support.rb: -------------------------------------------------------------------------------- 1 | unless Object.const_defined?("ActiveSupport") 2 | require File.expand_path("core_ext/string", File.dirname(__FILE__)) 3 | require File.expand_path("core_ext/array", File.dirname(__FILE__)) 4 | end 5 | 6 | require File.expand_path("core_ext/symbol", File.dirname(__FILE__)) -------------------------------------------------------------------------------- /lib/garb/version.rb: -------------------------------------------------------------------------------- 1 | module Garb 2 | module Version 3 | 4 | MAJOR = 0 5 | MINOR = 9 6 | TINY = 2 7 | 8 | def self.to_s # :nodoc: 9 | [MAJOR, MINOR, TINY].join('.') 10 | end 11 | 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/fixtures/cacert.pem: -------------------------------------------------------------------------------- 1 | ## 2 | ## cacert.pem-foo -- Bundle of CA Root Certificates 3 | ## 4 | ## Converted at: Thu Mar 26 21:23:06 2009 UTC 5 | ## 6 | ## This is a bundle of X.509 certificates of public Certificate Authorities 7 | ## (CA). These were automatically extracted from Mozilla's root certificates 8 | ## file (certdata.txt). This file can be found in the mozilla source tree: 9 | ## '/mozilla/security/nss/lib/ckfw/builtins/certdata.txt' 10 | ## 11 | ## It contains the certificates in PEM format and therefore 12 | ## can be directly used with curl / libcurl / php_curl, or with 13 | ## an Apache+mod_ssl webserver for SSL client authentication. 14 | ## Just configure this file as the SSLCACertificateFile. 15 | ## 16 | 17 | # ***** BEGIN LICENSE BLOCK ***** 18 | # Version: MPL 1.1/GPL 2.0/LGPL 2.1 19 | # 20 | # The contents of this file are subject to the Mozilla Public License Version 21 | # 1.1 (the "License"); you may not use this file except in compliance with 22 | # the License. You may obtain a copy of the License at 23 | # http://www.mozilla.org/MPL/ 24 | # 25 | # Software distributed under the License is distributed on an "AS IS" basis, 26 | # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 27 | # for the specific language governing rights and limitations under the 28 | # License. 29 | # 30 | # The Original Code is the Netscape security libraries. 31 | # 32 | # The Initial Developer of the Original Code is 33 | # Netscape Communications Corporation. 34 | # Portions created by the Initial Developer are Copyright (C) 1994-2000 35 | # the Initial Developer. All Rights Reserved. 36 | # 37 | # Contributor(s): 38 | # 39 | # Alternatively, the contents of this file may be used under the terms of 40 | # either the GNU General Public License Version 2 or later (the "GPL"), or 41 | # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 42 | # in which case the provisions of the GPL or the LGPL are applicable instead 43 | # of those above. If you wish to allow use of your version of this file only 44 | # under the terms of either the GPL or the LGPL, and not to allow others to 45 | # use your version of this file under the terms of the MPL, indicate your 46 | # decision by deleting the provisions above and replace them with the notice 47 | # and other provisions required by the GPL or the LGPL. If you do not delete 48 | # the provisions above, a recipient may use your version of this file under 49 | # the terms of any one of the MPL, the GPL or the LGPL. 50 | # 51 | # ***** END LICENSE BLOCK ***** 52 | # @(#) $RCSfile: certdata.txt,v $ $Revision: 1.51 $ $Date: 2009/01/15 22:35:15 $ 53 | 54 | Verisign/RSA Secure Server CA 55 | ============================= 56 | -----BEGIN CERTIFICATE----- 57 | MIICNDCCAaECEAKtZn5ORf5eV288mBle3cAwDQYJKoZIhvcNAQECBQAwXzELMAkGA1UEBhMCVVMx 58 | IDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQLEyVTZWN1cmUgU2VydmVy 59 | IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk0MTEwOTAwMDAwMFoXDTEwMDEwNzIzNTk1OVow 60 | XzELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1JTQSBEYXRhIFNlY3VyaXR5LCBJbmMuMS4wLAYDVQQL 61 | EyVTZWN1cmUgU2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGbMA0GCSqGSIb3DQEBAQUA 62 | A4GJADCBhQJ+AJLOesGugz5aqomDV6wlAXYMra6OLDfO6zV4ZFQD5YRAUcm/jwjiioII0haGN1Xp 63 | sSECrXZogZoFokvJSyVmIlZsiAeP94FZbYQHZXATcXY+m3dM41CJVphIuR2nKRoTLkoRWZweFdVJ 64 | VCxzOmmCsZc5nG1wZ0jl3S3WyB57AgMBAAEwDQYJKoZIhvcNAQECBQADfgBl3X7hsuyw4jrg7HFG 65 | mhkRuNPHoLQDQCYCPgmc4RKz0Vr2N6W3YQO2WxZpO8ZECAyIUwxrl0nHPjXcbLm7qt9cuzovk2C2 66 | qUtN8iD3zV9/ZHuO3ABc1/p3yjkWWW8O6tO1g39NTUJWdrTJXwT4OPjr0l91X817/OWOgHz8UA== 67 | -----END CERTIFICATE----- 68 | -------------------------------------------------------------------------------- /test/fixtures/profile_feed.json: -------------------------------------------------------------------------------- 1 | {"feed":{"id":"http://www.google.com/analytics/feeds/accounts/blah@gmail.com","updated":"2010-05-06T19:27:28.028-07:00","title":"Profile list for blah@gmail.com","link":{"rel":"self","type":"application/atom+xml","href":"https://www.google.com/analytics/feeds/accounts/default"},"author":{"name":"Google Analytics"},"generator":"Google Analytics","openSearch:totalResults":"2","openSearch:startIndex":"1","openSearch:itemsPerPage":"2","dxp$segment":[{"dxp$definition":null,"id":"gaid::-1","name":"All Visits"},{"dxp$definition":"ga:visitorType==New Visitor","id":"gaid::-2","name":"New Visitors"},{"dxp$definition":"ga:medium==cpa,ga:medium==cpc,ga:medium==cpm,ga:medium==cpp,ga:medium==cpv,ga:medium==ppc","id":"gaid::-3","name":"Paid Search Traffic"},{"dxp$definition":"ga:city!=New York","id":"gaid::0","name":"Not New York"}],"entry":[{"id":"http://www.google.com/analytics/feeds/accounts/ga:12345","updated":"2008-07-21T14:05:57.000-07:00","title":"Historical","link":{"rel":"alternate","type":"text/html","href":"http://www.google.com/analytics"},"ga:goal":[{"ga:destination":{"caseSensitive":"false","expression":"/blog.html","matchType":"head","step1Required":"false"},"active":"true","name":"Read Blog","number":"1","value":"10.0"},{"ga:destination":{"caseSensitive":"false","expression":"/support.html","matchType":"head","step1Required":"false"},"active":"true","name":"Go for Support","number":"2","value":"5.0"},{"ga:destination":{"ga:step":{"name":"Contact Form Page","number":"1","path":"/contact.html"},"caseSensitive":"false","expression":"/contact-submit","matchType":"exact","step1Required":"true"},"active":"true","name":"Contact Form Submission","number":"3","value":"100.0"},{"ga:destination":{"caseSensitive":"false","expression":"/newsletter-submit","matchType":"exact","step1Required":"false"},"active":"true","name":"Newsletter Form Submission","number":"4","value":"25.0"}],"dxp$property":[{"name":"ga:accountId","value":"1111"},{"name":"ga:accountName","value":"Blog Beta"},{"name":"ga:profileId","value":"1212"},{"name":"ga:webPropertyId","value":"UA-1111-1"},{"name":"ga:currency","value":"USD"},{"name":"ga:timezone","value":"America/New_York"}],"dxp:tableId":"ga:12345","gd:etag":"W/\"CUcHSXs8fip7I2A9WxBUFUg.\"","gd:kind":"analytics#account"},{"id":"http://www.google.com/analytics/feeds/accounts/ga:12346","updated":"2008-11-24T11:51:07.000-08:00","title":"Presently","link":{"rel":"alternate","type":"text/html","href":"http://www.google.com/analytics"},"ga:goal":[{"ga:destination":{"ga:step":{"name":"Contact Form Page","number":"1","path":"/contact.html"},"caseSensitive":"false","expression":"/contact-submit","matchType":"exact","step1Required":"true"},"active":"true","name":"Contact Form Submission","number":"1","value":"100.0"},{"ga:destination":{"caseSensitive":"false","expression":"/newsletter-submit","matchType":"exact","step1Required":"false"},"active":"true","name":"Newsletter Form Submission","number":"2","value":"25.0"}],"dxp$property":[{"name":"ga:accountId","value":"1111"},{"name":"ga:accountName","value":"Blog Beta"},{"name":"ga:profileId","value":"1213"},{"name":"ga:webPropertyId","value":"UA-1111-2"},{"name":"ga:currency","value":"USD"},{"name":"ga:timezone","value":"America/New_York"}],"dxp:tableId":"ga:12346","gd:etag":"W/\"A0UASX45cCp7I2A9WxFQEUQ.\"","gd:kind":"analytics#account"}],"xmlns":"http://www.w3.org/2005/Atom","xmlns:dxp":"http://schemas.google.com/analytics/2009","xmlns:ga":"http://schemas.google.com/ga/2009","xmlns:openSearch":"http://a9.com/-/spec/opensearch/1.1/","xmlns:gd":"http://schemas.google.com/g/2005","gd:etag":"W/\"A0UASX45cCp7I2A9WxFQEUQ.\"","gd:kind":"analytics#accounts"}} 2 | -------------------------------------------------------------------------------- /test/fixtures/profile_feed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | http://www.google.com/analytics/feeds/accounts/tpitale@gmail.com 4 | 2010-05-06T19:27:28.028-07:00 5 | Profile list for tpitale@gmail.com 6 | 7 | Google Analytics 8 | Google Analytics 9 | 2 10 | 1 11 | 2 12 | 13 | 14 | 15 | 16 | ga:visitorType==New Visitor 17 | 18 | 19 | ga:medium==cpa,ga:medium==cpc,ga:medium==cpm,ga:medium==cpp,ga:medium==cpv,ga:medium==ppc 20 | 21 | 22 | ga:city!=New York 23 | 24 | 25 | http://www.google.com/analytics/feeds/accounts/ga:12345 26 | 2008-07-21T14:05:57.000-07:00 27 | Historical 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | ga:12345 50 | 51 | 52 | http://www.google.com/analytics/feeds/accounts/ga:12346 53 | 2008-11-24T11:51:07.000-08:00 54 | Presently 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | ga:12346 71 | 72 | 73 | -------------------------------------------------------------------------------- /test/fixtures/report_feed.json: -------------------------------------------------------------------------------- 1 | {"feed":{"id":"http://www.google.com/analytics/feeds/data?ids=ga:123456&dimensions=ga:country,ga:city&metrics=ga:pageViews&start-date=2008-01-01&end-date=2008-01-02","updated":"2008-01-02T15:59:59.999-08:00 ","title":"Google Analytics Data for Profile 123456","link":[{"href":"http://www.google.com/analytics/feeds/data","rel":"http://schemas.google.com/g/2005#feed","type":"application/atom+xml"},{"href":"http://www.google.com/analytics/feeds/data?end-date=2008-01-02&start-date=2008-01-01&metrics=ga%3ApageViews&ids=ga%3A983247&dimensions=ga%3Acountry%2Cga%3Acity","rel":"self","type":"application/atom+xml"},{"href":"http://www.google.com/analytics/feeds/data?start-index=1001&max-results=1000&end-date=2008-01-02&start-date=2008-01-01&metrics=ga%3ApageViews&ids=ga%3A983247&dimensions=ga%3Acountry%2Cga%3Acity","rel":"next","type":"application/atom+xml"}],"author":{"name":"Google Analytics"},"openSearch:startIndex":"3","openSearch:itemsPerPage":"4","openSearch:totalResults":"18","dxp$containsSampledData":"true","ga:webPropertyID":"UA-123456-78","ga:start_date":"2008-01-01","ga:end_date":"2008-01-02","entry":[{"id":" http://www.google.com/analytics/feeds/data?ids=ga:1174&ga:country=%28not%20set%29&ga:city=%28not%20set%29&start-date=2008-01-01&end-date=2008-01-02 ","updated":" 2008-01-01T16:00:00.001-08:00 ","title":" ga:country=(not set) | ga:city=(not set) ","link":{"href":"http://www.google.com/analytics/feeds/data","rel":"self","type":"application/atom+xml"},"dxp$dimension":[{"name":"ga:country","value":"(not set)"},{"name":"ga:city","value":"(not set)"}],"dxp$metric":{"name":"ga:pageviews","value":"33"}},{"id":" http://www.google.com/analytics/feeds/data?ids=ga:1174&ga:country=Afghanistan&ga:city=Kabul&start-date=2008-01-01&end-date=2008-01-02 ","updated":" 2008-01-01T16:00:00.001-08:00 ","title":" ga:country=Afghanistan | ga:city=Kabul ","dxp$dimension":[{"name":"ga:country","value":"Afghanistan"},{"name":"ga:city","value":"Kabul"}],"dxp$metric":{"name":"ga:pageviews","value":"2"}},{"id":" http://www.google.com/analytics/feeds/data?ids=ga:1174&ga:country=Albania&ga:city=Tirana&start-date=2008-01-01&end-date=2008-01-02 ","updated":" 2008-01-01T16:00:00.001-08:00 ","title":" ga:country=Albania | ga:city=Tirana ","dxp$dimension":[{"name":"ga:country","value":"Albania"},{"name":"ga:city","value":"Tirana"}],"dxp$metric":{"name":"ga:pageviews","value":"1"}}],"xmlns":"http://www.w3.org/2005/Atom","xmlns:openSearch":"http://a9.com/-/spec/opensearchrss/1.0/","xmlns:dxp":"http://schemas.google.com/analytics/2009","xmlns:ga":"http://schemas.google.com/analytics/2008"}} 2 | -------------------------------------------------------------------------------- /test/fixtures/report_feed.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | http://www.google.com/analytics/feeds/data?ids=ga:983247&dimensions=ga:country,ga:city&metrics=ga:pageViews&start-date=2008-01-01&end-date=2008-01-02 7 | 2008-01-02T15:59:59.999-08:00 8 | Google Analytics Data for Profile 983247 9 | 10 | 11 | 12 | 13 | Google Analytics 14 | 15 | 3 16 | 4 17 | 18 18 | true 19 | UA-983247-67 20 | 2008-01-01 21 | 2008-01-02 22 | 23 | 24 | http://www.google.com/analytics/feeds/data?ids=ga:1174&ga:country=%28not%20set%29&ga:city=%28not%20set%29&start-date=2008-01-01&end-date=2008-01-02 25 | 2008-01-01T16:00:00.001-08:00 26 | ga:country=(not set) | ga:city=(not set) 27 | 28 | 29 | 30 | 31 | 32 | 33 | http://www.google.com/analytics/feeds/data?ids=ga:1174&ga:country=Afghanistan&ga:city=Kabul&start-date=2008-01-01&end-date=2008-01-02 34 | 2008-01-01T16:00:00.001-08:00 35 | ga:country=Afghanistan | ga:city=Kabul 36 | 37 | 38 | 39 | 40 | 41 | http://www.google.com/analytics/feeds/data?ids=ga:1174&ga:country=Albania&ga:city=Tirana&start-date=2008-01-01&end-date=2008-01-02 42 | 2008-01-01T16:00:00.001-08:00 43 | ga:country=Albania | ga:city=Tirana 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | begin 2 | require 'simplecov' 3 | SimpleCov.adapters.define 'garb' do 4 | add_filter '/test/' 5 | add_filter '/config/' 6 | add_filter 'version' 7 | 8 | add_group 'Libraries', 'lib' 9 | end 10 | 11 | SimpleCov.start 'garb' 12 | rescue LoadError 13 | puts "Install simplecov if you use 1.9 and want coverage metrics" 14 | end 15 | 16 | $:.reject! { |e| e.include? 'TextMate' } 17 | 18 | require 'bundler' 19 | Bundler.setup(:default, :test) 20 | 21 | require 'shoulda' 22 | require 'minitest/unit' 23 | require 'mocha' 24 | 25 | $:.unshift File.expand_path('../../lib', __FILE__) 26 | require 'garb' 27 | 28 | class MiniTest::Unit::TestCase 29 | include Shoulda::InstanceMethods 30 | extend Shoulda::ClassMethods 31 | include Shoulda::Assertions 32 | extend Shoulda::Macros 33 | include Shoulda::Helpers 34 | 35 | def read_fixture(filename) 36 | File.read(File.dirname(__FILE__) + "/fixtures/#{filename}") 37 | end 38 | 39 | def assert_data_params(expected) 40 | assert_received(Garb::Request::Data, :new) {|e| e.with(Garb::Session, Garb::Model::URL, expected)} 41 | end 42 | end 43 | 44 | MiniTest::Unit.autorun 45 | -------------------------------------------------------------------------------- /test/unit/garb/filter_parameters_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | class FilterParametersTest < MiniTest::Unit::TestCase 5 | # def self.should_define_operators(*operators) 6 | # operators.each do |operator| 7 | # should "create an operator and add to parameters for the #{operator} method" do 8 | # new_operator = stub 9 | # symbol = :foo 10 | # 11 | # SymbolOperator.expects(:new).with(:bar, operator).returns(new_operator) 12 | # @filter_parameters.filters do 13 | # send(operator.to_sym, :bar, 100) 14 | # end 15 | # 16 | # parameter = {new_operator => 100} 17 | # assert_equal parameter, @filter_parameters.parameters.last 18 | # end 19 | # end 20 | # end 21 | 22 | context "A FilterParameters" do 23 | context "when converting parameters hash into query string parameters" do 24 | should "parameterize hash operators and join elements with AND" do 25 | filters = FilterParameters.new({:city.eql => 'New York City', :state.eql => 'New York'}) 26 | 27 | params = ['ga:city%3D%3DNew+York+City', 'ga:state%3D%3DNew+York'] 28 | assert_equal params, filters.to_params['filters'].split('%3B').sort 29 | end 30 | 31 | should "properly encode operators" do 32 | filters = FilterParameters.new({:page_path.contains => 'New York'}) 33 | 34 | params = {'filters' => 'ga:pagePath%3D~New+York'} 35 | assert_equal params, filters.to_params 36 | end 37 | 38 | should "escape comma, semicolon, and backslash in values" do 39 | filters = FilterParameters.new({:url.eql => 'this;that,thing\other'}) 40 | 41 | params = {'filters' => 'ga:url%3D%3Dthis%5C%3Bthat%5C%2Cthing%5C%5Cother'} 42 | assert_equal params, filters.to_params 43 | end 44 | 45 | should "handle nested arrays mixed with hashes" do 46 | filters = FilterParameters.new([{:page_path.contains => 'NYC'}, [{:city.eql => 'New York City'}, {:state.eql => 'New York'}]]) 47 | 48 | params = ['ga:pagePath%3D~NYC,ga:city%3D%3DNew+York+City,ga:state%3D%3DNew+York'] 49 | assert_equal params, filters.to_params['filters'].split('%3B').sort 50 | end 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/unit/garb/management/account_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | module Management 5 | class AccountTest < MiniTest::Unit::TestCase 6 | context "The Account class" do 7 | should "turn entries for path into array of accounts" do 8 | feed = stub(:entries => ["entry1"]) 9 | Feed.stubs(:new).returns(feed) 10 | 11 | Account.stubs(:new_from_entry) 12 | Account.all 13 | 14 | assert_received(Feed, :new) {|e| e.with(Session, '/accounts')} 15 | assert_received(feed, :entries) 16 | assert_received(Account, :new_from_entry) {|e| e.with("entry1", Session)} 17 | end 18 | end 19 | 20 | context "an Account" do 21 | setup do 22 | entry = { 23 | "title" => "Google Analytics Account Garb", 24 | "link" => [{"rel" => "self", "href" => Feed::BASE_URL+"/accounts/123456"}], 25 | "dxp$property" => [ 26 | {"name" => "ga:accountId", "value" => "123456"}, 27 | {"name" => "ga:accountName", "value" => "Garb"} 28 | ] 29 | } 30 | @account = Account.new_from_entry(entry, Session) 31 | end 32 | 33 | should "extract id and title from GA entry" do 34 | assert_equal "Garb", @account.title 35 | assert_equal "123456", @account.id 36 | end 37 | 38 | should "extract a name from GA entry properties" do 39 | assert_equal "Garb", @account.name 40 | end 41 | 42 | should "combine the Account.path and the id into an new path" do 43 | assert_equal "/accounts/123456", @account.path 44 | end 45 | 46 | should "have a reference to the session it was created with" do 47 | assert_equal Session, @account.session 48 | end 49 | 50 | should "have web properties" do 51 | WebProperty.stubs(:for_account) 52 | @account.web_properties 53 | assert_received(WebProperty, :for_account) {|e| e.with(@account)} 54 | end 55 | 56 | should "have profiles" do 57 | Profile.stubs(:for_account) 58 | @account.profiles 59 | assert_received(Profile, :for_account) {|e| e.with(@account)} 60 | end 61 | 62 | should "have goals" do 63 | Goal.stubs(:for_account) 64 | @account.goals 65 | assert_received(Goal, :for_account) {|e| e.with(@account)} 66 | end 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /test/unit/garb/management/feed_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | module Management 5 | class FeedTest < MiniTest::Unit::TestCase 6 | context "a Feed" do 7 | setup do 8 | @request = stub 9 | Request::Data.stubs(:new).returns(@request) 10 | @feed = Feed.new(Garb::Session.new, '/accounts') 11 | end 12 | 13 | should "have a parsed response" do 14 | JSON.stubs(:parse) 15 | @feed.stubs(:response).returns(stub(:body => 'response body')) 16 | @feed.parsed_response 17 | 18 | assert_received(JSON, :parse) {|e| e.with('response body')} 19 | end 20 | 21 | should "have entries from the parsed response" do 22 | @feed.stubs(:parsed_response).returns({'feed' => {'entry' => ['entry1', 'entry2']}}) 23 | assert_equal ['entry1', 'entry2'], @feed.entries 24 | end 25 | 26 | should "handle case of a single entry" do 27 | @feed.stubs(:parsed_response).returns({'feed' => {'entry' => {'profile_id' => '12345'}}}) 28 | assert_equal [{'profile_id' => '12345'}], @feed.entries 29 | end 30 | 31 | should "have an empty array for entries without a response" do 32 | @feed.stubs(:parsed_response).returns(nil) 33 | assert_equal [], @feed.entries 34 | end 35 | 36 | should "have a response from the request" do 37 | @request.stubs(:send_request) 38 | @feed.response 39 | assert_received(@request, :send_request) 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/unit/garb/management/goal_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | module Management 5 | class GoalTest < MiniTest::Unit::TestCase 6 | context "the Goal class" do 7 | should "turn entries for path into array of goals" do 8 | feed = stub(:entries => ["entry1"]) 9 | Feed.stubs(:new).returns(feed) 10 | 11 | Goal.stubs(:new_from_entry) 12 | Goal.all 13 | 14 | assert_received(Feed, :new) {|e| e.with(Session, '/accounts/~all/webproperties/~all/profiles/~all/goals')} 15 | assert_received(feed, :entries) 16 | assert_received(Goal, :new_from_entry) {|e| e.with("entry1", Session)} 17 | end 18 | 19 | should "find all goals for a given account" do 20 | Goal.stubs(:all) 21 | Goal.for_account(stub(:session => 'session', :path => '/accounts/123')) 22 | assert_received(Goal, :all) {|e| e.with('session', '/accounts/123/webproperties/~all/profiles/~all/goals')} 23 | end 24 | 25 | should "find all goals for a given web_property" do 26 | Goal.stubs(:all) 27 | Goal.for_web_property(stub(:session => 'session', :path => '/accounts/123/webproperties/456')) 28 | assert_received(Goal, :all) {|e| e.with('session', '/accounts/123/webproperties/456/profiles/~all/goals')} 29 | end 30 | 31 | should "find all goals for a given profile" do 32 | Goal.stubs(:all) 33 | Goal.for_profile(stub(:session => 'session', :path => '/accounts/123/webproperties/456/profiles/789')) 34 | assert_received(Goal, :all) {|e| e.with('session', '/accounts/123/webproperties/456/profiles/789/goals')} 35 | end 36 | end 37 | 38 | context "a Goal" do 39 | setup do 40 | entry = { 41 | "link" => [{"rel" => "self", "href" => Feed::BASE_URL+"/accounts/1189765/webproperties/UA-1189765-1/profiles/98765"}], 42 | "ga:goal" => { 43 | "ga:destination"=>{"caseSensitive"=>"false","expression"=>"/blog.html","matchType"=>"head"}, 44 | "active"=>"true", 45 | "name"=>"Read Blog", 46 | "number"=>"1", 47 | "value"=>"10.0" 48 | } 49 | } 50 | 51 | @goal = Goal.new_from_entry(entry, Session) 52 | end 53 | 54 | should "have a name" do 55 | assert_equal "Read Blog", @goal.name 56 | end 57 | 58 | should "have a number" do 59 | assert_equal 1, @goal.number 60 | end 61 | 62 | should "have a value" do 63 | assert_equal 10.0, @goal.value 64 | end 65 | 66 | should "know if it is active" do 67 | assert_equal true, @goal.active? 68 | end 69 | 70 | should "know if it is not active" do 71 | @goal.active = false 72 | assert_equal false, @goal.active? 73 | end 74 | 75 | should "have a destination" do 76 | assert_equal true, @goal.destination.is_a?(Destination) 77 | end 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/unit/garb/management/profile_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | module Management 5 | class ProfileTest < MiniTest::Unit::TestCase 6 | context "The Profile class" do 7 | should "turn entries for path into array of profiles" do 8 | feed = stub(:entries => ["entry1"]) 9 | Feed.stubs(:new).returns(feed) 10 | 11 | Profile.stubs(:new_from_entry) 12 | Profile.all 13 | 14 | assert_received(Feed, :new) {|e| e.with(Session, '/accounts/~all/webproperties/~all/profiles')} 15 | assert_received(feed, :entries) 16 | assert_received(Profile, :new_from_entry) {|e| e.with("entry1", Session)} 17 | end 18 | 19 | should "find all profiles for a given account" do 20 | Profile.stubs(:all) 21 | Profile.for_account(stub(:session => 'session', :path => '/accounts/123')) 22 | assert_received(Profile, :all) {|e| e.with('session', '/accounts/123/webproperties/~all/profiles')} 23 | end 24 | 25 | should "find all profiles for a given web_property" do 26 | Profile.stubs(:all) 27 | Profile.for_web_property(stub(:session => 'session', :path => '/accounts/123/webproperties/456')) 28 | assert_received(Profile, :all) {|e| e.with('session', '/accounts/123/webproperties/456/profiles')} 29 | end 30 | end 31 | 32 | context "A Profile" do 33 | setup do 34 | entry = { 35 | "link" => [{"rel" => "self", "href" => Feed::BASE_URL+"/accounts/1189765/webproperties/UA-1189765-1/profiles/98765"}], 36 | "dxp$property" => [ 37 | {"name" => "ga:profileId", "value" => "98765"}, 38 | {"name" => "ga:accountId", "value" => "1189765"}, 39 | {"name" => "ga:webPropertyId", "value" => 'UA-1189765-1'}, 40 | {"name" => "ga:profileName", "value" => "example.com"}, 41 | {"name"=>"dxp:tableId", "value"=>"ga:4506"}, 42 | {"name"=>"ga:currency", "value"=>"USD"}, 43 | {"name"=>"ga:timezone", "value"=>"America/New_York"} 44 | ] 45 | } 46 | @profile = Profile.new_from_entry(entry, Session) 47 | end 48 | 49 | should "have a title" do 50 | assert_equal "example.com", @profile.title 51 | end 52 | 53 | should "have an id" do 54 | assert_equal '98765', @profile.id 55 | end 56 | 57 | should "have an account_id" do 58 | assert_equal '1189765', @profile.account_id 59 | end 60 | 61 | should "have a web_property_id" do 62 | assert_equal 'UA-1189765-1', @profile.web_property_id 63 | end 64 | 65 | should "have a table_id (for old Garb::Report)" do 66 | assert_equal 'ga:4506', @profile.table_id 67 | end 68 | 69 | should "have a path" do 70 | assert_equal "/accounts/1189765/webproperties/UA-1189765-1/profiles/98765", @profile.path 71 | end 72 | 73 | should "have goals" do 74 | Goal.stubs(:for_profile) 75 | @profile.goals 76 | assert_received(Goal, :for_profile) {|e| e.with(@profile)} 77 | end 78 | end 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/unit/garb/management/segment_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | module Management 5 | class SegmentTest < MiniTest::Unit::TestCase 6 | context "The Segment class" do 7 | should "turn entries for path into array of accounts" do 8 | feed = stub(:entries => ["entry1"]) 9 | Feed.stubs(:new).returns(feed) 10 | 11 | Segment.stubs(:new_from_entry) 12 | Segment.all 13 | 14 | assert_received(Feed, :new) {|e| e.with(Session, '/segments')} 15 | assert_received(feed, :entries) 16 | assert_received(Segment, :new_from_entry) {|e| e.with("entry1", Session)} 17 | end 18 | end 19 | 20 | context "A Segment" do 21 | setup do 22 | entry = { 23 | "link" => [{"rel" => "self", "href" => Feed::BASE_URL+"/segments/12"}], 24 | "dxp:segment" => { 25 | "id" => "gaid::-3", 26 | "name" => "Returning Visitor", 27 | "dxp:definition" => "ga:visitorType==Returning Visitor" 28 | } 29 | } 30 | @segment = Segment.new_from_entry(entry, Session) 31 | end 32 | 33 | should "have an id" do 34 | assert_equal "gaid::-3", @segment.id 35 | end 36 | 37 | should "have a name" do 38 | assert_equal "Returning Visitor", @segment.name 39 | end 40 | 41 | should "have a definition" do 42 | assert_equal "ga:visitorType==Returning Visitor", @segment.definition 43 | end 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/unit/garb/management/web_property_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | module Management 5 | class WebPropertyTest < MiniTest::Unit::TestCase 6 | context "the WebProperty class" do 7 | should "turn entries for path into array of web properties" do 8 | feed = stub(:entries => ["entry1"]) 9 | Feed.stubs(:new).returns(feed) 10 | 11 | WebProperty.stubs(:new_from_entry) 12 | WebProperty.all 13 | 14 | assert_received(Feed, :new) {|e| e.with(Session, '/accounts/~all/webproperties')} 15 | assert_received(feed, :entries) 16 | assert_received(WebProperty, :new_from_entry) {|e| e.with("entry1", Session)} 17 | end 18 | 19 | should "find all web properties for a given account" do 20 | WebProperty.stubs(:all) 21 | WebProperty.for_account(stub(:session => Session, :path => '/accounts/123456')) 22 | 23 | assert_received(WebProperty, :all) do |e| 24 | e.with(Session, '/accounts/123456/webproperties') 25 | end 26 | end 27 | end 28 | 29 | context "a WebProperty" do 30 | setup do 31 | entry = { 32 | "link" => [{"rel" => "self", "href" => Feed::BASE_URL+"/accounts/1189765/webproperties/UA-1189765-1"}], 33 | "dxp$property" => [ 34 | {"name" => "ga:accountId", "value" => "1189765"}, 35 | {"name" => "ga:webPropertyId", "value" => 'UA-1189765-1'} 36 | ] 37 | } 38 | 39 | @web_property = WebProperty.new_from_entry(entry, Session) 40 | end 41 | 42 | should "have an id" do 43 | assert_equal "UA-1189765-1", @web_property.id 44 | end 45 | 46 | should "have an account_id" do 47 | assert_equal "1189765", @web_property.account_id 48 | end 49 | 50 | should "have profiles" do 51 | Profile.stubs(:for_web_property) 52 | @web_property.profiles 53 | assert_received(Profile, :for_web_property) {|e| e.with(@web_property)} 54 | end 55 | 56 | should "have goals" do 57 | Goal.stubs(:for_web_property) 58 | @web_property.goals 59 | assert_received(Goal, :for_web_property) {|e| e.with(@web_property)} 60 | end 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/unit/garb/model_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ResultKlass 4 | def initialize(attrs) 5 | end 6 | end 7 | 8 | module Garb 9 | class ModelTest < MiniTest::Unit::TestCase 10 | context "A class extended with Garb::Model" do 11 | setup do 12 | @test_model = Class.new 13 | @test_model.extend(Garb::Model) 14 | end 15 | 16 | # public API 17 | should "be able to define metrics" do 18 | report_parameter = stub(:<<) 19 | ReportParameter.stubs(:new).returns(report_parameter) 20 | 21 | @test_model.metrics :visits, :pageviews 22 | 23 | assert_received(ReportParameter, :new) {|e| e.with(:metrics)} 24 | assert_received(report_parameter, :<<) {|e| e.with([:visits, :pageviews])} 25 | end 26 | 27 | should "be able to define dimensions" do 28 | report_parameter = stub(:<<) 29 | ReportParameter.stubs(:new).returns(report_parameter) 30 | 31 | @test_model.dimensions :page_path, :event_category 32 | 33 | assert_received(ReportParameter, :new) {|e| e.with(:dimensions)} 34 | assert_received(report_parameter, :<<) {|e| e.with([:page_path, :event_category])} 35 | end 36 | 37 | should "be able to se the instance klass" do 38 | @test_model.set_instance_klass ResultKlass 39 | assert_equal ResultKlass, @test_model.instance_klass 40 | end 41 | 42 | context "with a profile" do 43 | setup do 44 | entry = { 45 | "title" => "Google Analytics Profile example.com", 46 | "link" => [{"rel" => "self", "href" => Garb::Management::Feed::BASE_URL+"/accounts/1189765/webproperties/UA-1189765-1/profiles/98765"}], 47 | "dxp$property" => [ 48 | {"name" => "ga:profileId", "value" => "98765"}, 49 | {"name" => "ga:accountId", "value" => "1189765"}, 50 | {"name" => "ga:webPropertyId", "value" => 'UA-1189765-1'} 51 | ] 52 | } 53 | 54 | @profile = Garb::Management::Profile.new_from_entry(entry, Session) 55 | end 56 | 57 | context "when getting results" do 58 | setup do 59 | @response = stub(:body => "raw report data") 60 | Request::Data.stubs(:new).returns(stub(:send_request => @response)) 61 | ReportResponse.stubs(:new).returns(stub(:results => ['result'])) 62 | 63 | @test_model.stubs(:metrics).returns(stub(:to_params => {'metrics' => 'ga:visits'})) 64 | @test_model.stubs(:dimensions).returns(stub(:to_params => {'dimensions' => 'ga:pagePath'})) 65 | 66 | now = Time.now 67 | Time.stubs(:now).returns(now) 68 | 69 | # p @profile.id 70 | 71 | @params = {'ids' => Garb.to_ga(@profile.id), 72 | 'start-date' => (now - Model::MONTH).strftime('%Y-%m-%d'), 73 | 'end-date' => now.strftime('%Y-%m-%d'), 74 | 'metrics' => 'ga:visits', 75 | 'dimensions' => 'ga:pagePath'} 76 | end 77 | 78 | should "get all results" do 79 | assert_equal ['result'], @test_model.results(@profile) 80 | assert_received(ReportResponse, :new) {|e| e.with("raw report data", OpenStruct)} 81 | assert_data_params(@params) 82 | end 83 | 84 | should "be able to filter" do 85 | FilterParameters.stubs(:new).returns(stub(:to_params => {'filters' => "params"})) 86 | assert_equal ['result'], @test_model.results(@profile, :filters => {:page_path => '/'}) 87 | 88 | assert_data_params(@params.merge({'filters' => 'params'})) 89 | assert_received(FilterParameters, :new) {|e| e.with({:page_path => '/'})} 90 | end 91 | 92 | should "be able to set the filter segment by id" do 93 | assert_equal ['result'], @test_model.results(@profile, :segment_id => 1) 94 | assert_data_params(@params.merge({'segment' => 'gaid::1'})) 95 | end 96 | 97 | should "be able to sort" do 98 | sort_parameter = stub(:<<) 99 | sort_parameter.stubs(:to_params => {'sort' => 'sort value'}) 100 | ReportParameter.stubs(:new).returns(sort_parameter) 101 | 102 | assert_equal ['result'], @test_model.results(@profile, :sort => [:visits]) 103 | assert_received(sort_parameter, :<<) {|e| e.with([:visits])} 104 | assert_data_params(@params.merge({'sort' => 'sort value'})) 105 | end 106 | 107 | should "be able to limit" do 108 | assert_equal ['result'], @test_model.results(@profile, :limit => 20) 109 | assert_data_params(@params.merge({'max-results' => 20})) 110 | end 111 | 112 | should "be able to offset" do 113 | assert_equal ['result'], @test_model.results(@profile, :offset => 10) 114 | assert_data_params(@params.merge({'start-index' => 10})) 115 | end 116 | 117 | should "be able to shift the date range" do 118 | start_date = (Time.now - 1296000) 119 | end_date = Time.now 120 | 121 | assert_equal ['result'], @test_model.results(@profile, :start_date => start_date, :end_date => end_date) 122 | assert_data_params(@params.merge({'start-date' => start_date.strftime('%Y-%m-%d'), 'end-date' => end_date.strftime('%Y-%m-%d')})) 123 | end 124 | 125 | should "return a set of results in the defined class" do 126 | @test_model.stubs(:instance_klass).returns(ResultKlass) 127 | 128 | assert_equal ['result'], @test_model.results(@profile) 129 | assert_received(ReportResponse, :new) {|e| e.with("raw report data", ResultKlass)} 130 | end 131 | end 132 | 133 | # should "return results as an array of the class it belongs to, if that class is an ActiveRecord descendant" 134 | # should "return results as an array of the class it belongs to, if that class is a DataMapper descendant" 135 | # should "return results as an array of the class it belongs to, if that class is a MongoMapper descendant" 136 | # should "return results as an array of the class it belongs to, if that class is a Mongoid descendant" 137 | end 138 | end 139 | end 140 | end 141 | -------------------------------------------------------------------------------- /test/unit/garb/oauth_session_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | class OAuthSessionTest < MiniTest::Unit::TestCase 5 | context "An instance of OAuthSession" do 6 | should "have tests" do 7 | assert true 8 | end 9 | end 10 | end 11 | end -------------------------------------------------------------------------------- /test/unit/garb/profile_reports_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | 5 | Exits = Class.new 6 | 7 | class FakeProfile 8 | include ProfileReports 9 | end 10 | 11 | class ProfileReportsTest < MiniTest::Unit::TestCase 12 | context "The ProfileReports module" do 13 | should "define a new method when given a class" do 14 | ProfileReports.add_report_method(Exits) 15 | assert_equal true, FakeProfile.new.respond_to?(:exits) 16 | end 17 | 18 | should "return results from the given class with options" do 19 | results = [1,2,3] 20 | Exits.stubs(:results).returns(results) 21 | ProfileReports.add_report_method(Exits) 22 | 23 | profile = FakeProfile.new 24 | assert_equal results, profile.exits(:start => "now") 25 | assert_received(Exits, :results) {|e| e.with(profile, :start => "now")} 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/unit/garb/report_parameter_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | class ReportParameterTest < MiniTest::Unit::TestCase 5 | 6 | context "An instance of the ReportParameter class" do 7 | setup do 8 | @metrics = ReportParameter.new(:metrics) 9 | end 10 | 11 | should "have a name" do 12 | assert_equal "metrics", @metrics.name 13 | end 14 | 15 | should "have a list of elements" do 16 | assert_equal [], @metrics.elements 17 | end 18 | 19 | should "be able to add new elements" do 20 | assert_equal(@metrics, @metrics << :page_path) 21 | assert_equal [:page_path], @metrics.elements 22 | end 23 | 24 | should "merge an array of elements" do 25 | assert_equal(@metrics, @metrics << [:page_path]) 26 | assert_equal [:page_path], @metrics.elements 27 | end 28 | 29 | context "converting to params" do 30 | should "be able to format the parameters into strings" do 31 | @metrics << :page_path 32 | assert_equal({'metrics' => 'ga:pagePath'}, @metrics.to_params) 33 | end 34 | 35 | should "join multiple symbol elements" do 36 | @metrics << :page_path << :city 37 | assert_equal({'metrics' => 'ga:pagePath,ga:city'}, @metrics.to_params) 38 | end 39 | end 40 | end 41 | 42 | end 43 | end -------------------------------------------------------------------------------- /test/unit/garb/report_response_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | SpecialKlass = Class.new(OpenStruct) 5 | 6 | class ReportResponseTest < MiniTest::Unit::TestCase 7 | context "A ReportResponse" do 8 | context "with a report feed" do 9 | setup do 10 | @json = File.read(File.join(File.dirname(__FILE__), '..', '..', "/fixtures/report_feed.json")) 11 | end 12 | 13 | should "parse results from atom xml" do 14 | response = ReportResponse.new(@json) 15 | assert_equal ['33', '2', '1'], response.results.map(&:pageviews) 16 | end 17 | 18 | should "default to returning an array of OpenStruct objects" do 19 | response = ReportResponse.new(@json) 20 | assert_equal [OpenStruct, OpenStruct, OpenStruct], response.results.map(&:class) 21 | end 22 | 23 | should "return an array of instances of a specified class" do 24 | response = ReportResponse.new(@json, SpecialKlass) 25 | assert_equal [SpecialKlass, SpecialKlass, SpecialKlass], response.results.map(&:class) 26 | end 27 | 28 | should "know the total number of results" do 29 | response = ReportResponse.new(@json) 30 | assert_equal 18, response.results.total_results 31 | end 32 | 33 | should "know if the data has been sampled" do 34 | response = ReportResponse.new(@json) 35 | assert_equal true, response.results.sampled? 36 | end 37 | end 38 | 39 | should "return an empty array if there are no results" do 40 | response = ReportResponse.new("result json") 41 | JSON.stubs(:parse).with("result json").returns({'feed' => {'entry' => nil}}) 42 | 43 | assert_equal [], response.results.to_a 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/unit/garb/request/authentication_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | CA_CERT_FILE = File.join(File.dirname(__FILE__), '..', '/cacert.pem') 4 | 5 | module Garb 6 | module Request 7 | class AuthenticationTest < MiniTest::Unit::TestCase 8 | 9 | context "An instance of the Request::Authentication class" do 10 | 11 | setup { @request = Request::Authentication.new('email', 'password') } 12 | teardown do 13 | Garb.proxy_address = nil 14 | Garb.proxy_port = nil 15 | end 16 | 17 | should "have a collection of parameters that include the email and password" do 18 | expected = 19 | { 20 | 'Email' => 'user@example.com', 21 | 'Passwd' => 'fuzzybunnies', 22 | 'accountType' => 'HOSTED_OR_GOOGLE', 23 | 'service' => 'analytics', 24 | 'source' => 'vigetLabs-garb-001' 25 | } 26 | 27 | request = Request::Authentication.new('user@example.com', 'fuzzybunnies') 28 | assert_equal expected, request.parameters 29 | end 30 | 31 | should "have a URI" do 32 | assert_equal URI.parse('https://www.google.com/accounts/ClientLogin'), @request.uri 33 | end 34 | 35 | should "be able to send a request to the GAAPI service with proper ssl" do 36 | @request.expects(:build_request).returns('post') 37 | 38 | response = mock {|m| m.expects(:is_a?).with(Net::HTTPOK).returns(true) } 39 | 40 | http = mock do |m| 41 | m.expects(:read_timeout=).with(Garb.read_timeout) 42 | m.expects(:use_ssl=).with(true) 43 | m.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) 44 | m.expects(:ca_file=).with(CA_CERT_FILE) 45 | m.expects(:request).with('post').yields(response) 46 | end 47 | 48 | Net::HTTP.expects(:new).with('www.google.com', 443, nil, nil).returns(http) 49 | 50 | @request.send_request(OpenSSL::SSL::VERIFY_PEER) 51 | end 52 | 53 | should "be able to send a request to the GAAPI service with ignoring ssl" do 54 | @request.expects(:build_request).returns('post') 55 | 56 | response = mock {|m| m.expects(:is_a?).with(Net::HTTPOK).returns(true) } 57 | 58 | http = mock do |m| 59 | m.expects(:read_timeout=).with(Garb.read_timeout) 60 | m.expects(:use_ssl=).with(true) 61 | m.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE) 62 | m.expects(:request).with('post').yields(response) 63 | end 64 | 65 | Net::HTTP.expects(:new).with('www.google.com', 443, nil, nil).returns(http) 66 | 67 | @request.send_request(OpenSSL::SSL::VERIFY_NONE) 68 | end 69 | 70 | should "be able to build a request for the GAAPI service" do 71 | params = "param" 72 | @request.expects(:parameters).with().returns(params) 73 | 74 | post = mock 75 | post.expects(:set_form_data).with(params) 76 | 77 | Net::HTTP::Post.expects(:new).with('/accounts/ClientLogin').returns(post) 78 | 79 | @request.build_request 80 | end 81 | 82 | should "be able to retrieve an auth_token from the body" do 83 | response_data = 84 | "SID=mysid\n" + 85 | "LSID=mylsid\n" + 86 | "Auth=auth_token\n" 87 | 88 | @request.expects(:send_request).with(OpenSSL::SSL::VERIFY_NONE).returns(stub(:body => response_data)) 89 | 90 | assert_equal 'auth_token', @request.auth_token 91 | end 92 | 93 | should "use VERIFY_PEER if auth_token needs to be secure" do 94 | response_data = 95 | "SID=mysid\n" + 96 | "LSID=mylsid\n" + 97 | "Auth=auth_token\n" 98 | 99 | @request.expects(:send_request).with(OpenSSL::SSL::VERIFY_PEER).returns(stub(:body => response_data)) 100 | 101 | assert_equal 'auth_token', @request.auth_token(:secure => true) 102 | end 103 | 104 | should "raise an exception when requesting an auth_token when the authorization fails" do 105 | @request.stubs(:build_request) 106 | response = mock do |m| 107 | m.expects(:is_a?).with(Net::HTTPOK).returns(false) 108 | end 109 | 110 | http = stub do |s| 111 | s.stubs(:use_ssl=) 112 | s.expects(:read_timeout=).with(Garb.read_timeout) 113 | s.stubs(:verify_mode=) 114 | s.stubs(:request).yields(response) 115 | end 116 | 117 | Net::HTTP.stubs(:new).with('www.google.com', 443, nil, nil).returns(http) 118 | 119 | assert_raises(Garb::Request::Authentication::AuthError) do 120 | @request.send_request(OpenSSL::SSL::VERIFY_NONE) 121 | end 122 | end 123 | 124 | should "make use of Garb proxy settings in Net::HTTP request" do 125 | @request.expects(:build_request).returns('post') 126 | 127 | response = stub {|s| s.stubs(:is_a?).returns(true) } 128 | 129 | http = mock do |m| 130 | m.stubs(:read_timeout=) 131 | m.stubs(:use_ssl=) 132 | m.stubs(:verify_mode=) 133 | m.stubs(:request).yields(response) 134 | end 135 | 136 | Garb.proxy_address = '127.0.0.1' 137 | Garb.proxy_port = '1234' 138 | 139 | Net::HTTP.expects(:new).with('www.google.com', 443, '127.0.0.1', '1234').returns(http) 140 | 141 | @request.send_request(nil) 142 | end 143 | end 144 | end 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /test/unit/garb/request/data_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | module Request 5 | class DataTest < MiniTest::Unit::TestCase 6 | 7 | context "An instance of the Request::Data class" do 8 | setup do 9 | @session = Session.new 10 | @session.auth_token = 'abcdefg123456' 11 | end 12 | 13 | teardown do 14 | Garb.proxy_address = nil 15 | Garb.proxy_port = nil 16 | end 17 | 18 | should "be able to build the query string from parameters" do 19 | parameters = {'ids' => '12345', 'metrics' => 'country'} 20 | data_request = Request::Data.new(@session, "", parameters) 21 | 22 | query_string = data_request.query_string 23 | 24 | assert_match(/^\?/, query_string) 25 | 26 | query_string.sub!(/^\?/, '') 27 | 28 | assert_equal ["alt=json", "ids=12345", "metrics=country"], query_string.split('&').sort 29 | end 30 | 31 | should "only contain JSON response option if parameters are empty" do 32 | data_request = Request::Data.new(@session, "") 33 | assert_equal "?alt=json", data_request.query_string 34 | end 35 | 36 | should "be able to build a uri" do 37 | url = 'http://example.com' 38 | assert_equal URI.parse(url), Request::Data.new(@session, url).uri 39 | end 40 | 41 | should "be able to send a request for a single user" do 42 | @session.stubs(:single_user?).returns(true) 43 | response = mock('Net::HTTPOK') do |m| 44 | m.expects(:kind_of?).with(Net::HTTPSuccess).returns(true) 45 | end 46 | 47 | data_request = Request::Data.new(@session, 'https://example.com/data', 'key' => 'value') 48 | data_request.stubs(:single_user_request).returns(response) 49 | data_request.send_request 50 | 51 | assert_received(data_request, :single_user_request) 52 | end 53 | 54 | should "be able to send a request for an oauth user" do 55 | @session.stubs(:single_user?).returns(false) 56 | @session.stubs(:oauth_user?).returns(true) 57 | response = mock('Net::HTTPOK') do |m| 58 | m.expects(:kind_of?).with(Net::HTTPSuccess).returns(true) 59 | end 60 | 61 | data_request = Request::Data.new(@session, 'https://example.com/data', 'key' => 'value') 62 | data_request.stubs(:oauth_user_request).returns(response) 63 | data_request.send_request 64 | 65 | assert_received(data_request, :oauth_user_request) 66 | end 67 | 68 | should "raise if the request is unauthorized" do 69 | @session.stubs(:single_user?).returns(false) 70 | @session.stubs(:oauth_user?).returns(true) 71 | response = mock('Net::HTTPUnauthorized', :body => 'Error') 72 | 73 | data_request = Request::Data.new(@session, 'https://example.com/data', 'key' => 'value') 74 | data_request.stubs(:oauth_user_request).returns(response) 75 | 76 | assert_raises(Garb::Request::Data::ClientError) do 77 | data_request.send_request 78 | end 79 | end 80 | 81 | should "be able to request via the ouath access token" do 82 | access_token = stub(:get => "responseobject") 83 | @session.stubs(:access_token).returns(access_token) 84 | 85 | data_request = Request::Data.new(@session, 'https://example.com/data', 'key' => 'value') 86 | assert_equal 'responseobject', data_request.oauth_user_request 87 | 88 | assert_received(@session, :access_token) 89 | assert_received(access_token, :get) {|e| e.with('https://example.com/data?key=value&alt=json', {'GData-Version' => '2'})} 90 | end 91 | 92 | should "be able to request via http with an auth token" do 93 | @session.expects(:auth_token).with().returns('toke') 94 | response = mock 95 | 96 | http = mock do |m| 97 | m.expects(:use_ssl=).with(true) 98 | m.expects(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE) 99 | m.expects(:get).with('/data?key=value&alt=json', { 100 | 'Authorization' => 'GoogleLogin auth=toke', 101 | 'GData-Version' => '2' 102 | }).returns(response) 103 | end 104 | 105 | Garb.proxy_address = "127.0.0.1" 106 | Garb.proxy_port = "1234" 107 | Net::HTTP.expects(:new).with('example.com', 443, "127.0.0.1", "1234").returns(http) 108 | 109 | data_request = Request::Data.new(@session, 'https://example.com/data', 'key' => 'value') 110 | assert_equal response, data_request.single_user_request 111 | end 112 | end 113 | 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /test/unit/garb/session_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | module Garb 4 | class SessionTest < MiniTest::Unit::TestCase 5 | 6 | context "The Session class" do 7 | 8 | should "be able retrieve an auth_token for a user" do 9 | auth_request = mock {|m| m.expects(:auth_token).with({}).returns('toke') } 10 | Request::Authentication.expects(:new).with('email', 'password', {}).returns(auth_request) 11 | 12 | Session.login('email', 'password') 13 | assert_equal 'toke', Session.auth_token 14 | end 15 | 16 | should "be able retrieve an auth_token for a user with secure ssl" do 17 | opts = {:secure => true, :account_type => 'GOOGLE'} 18 | auth_request = mock {|m| m.expects(:auth_token).with(opts).returns('toke') } 19 | Request::Authentication.expects(:new).with('email', 'password', opts).returns(auth_request) 20 | 21 | Session.login('email', 'password', opts) 22 | assert_equal 'toke', Session.auth_token 23 | end 24 | 25 | should "retain the email address for this session" do 26 | Request::Authentication.stubs(:new).returns(stub(:auth_token => 'toke')) 27 | 28 | Session.login('email', 'password') 29 | assert_equal 'email', Session.email 30 | end 31 | 32 | should "know if the Session is for a single user" do 33 | Session.auth_token = "abcdefg1234567" 34 | assert_equal true, Session.single_user? 35 | end 36 | 37 | should "know if the Session is for oauth" do 38 | Session.access_token = 'some_oauth_access_token' 39 | assert_equal true, Session.oauth_user? 40 | end 41 | end 42 | 43 | context "A Session" do 44 | setup do 45 | @session = Session.new 46 | end 47 | 48 | should "be able retrieve an auth_token for a user" do 49 | auth_request = mock {|m| m.expects(:auth_token).with({}).returns('toke') } 50 | Request::Authentication.expects(:new).with('email', 'password', {}).returns(auth_request) 51 | 52 | @session.login('email', 'password') 53 | assert_equal 'toke', @session.auth_token 54 | end 55 | 56 | should "be able retrieve an auth_token for a user with secure ssl" do 57 | opts = {:secure => true, :account_type => 'GOOGLE'} 58 | auth_request = mock {|m| m.expects(:auth_token).with(opts).returns('toke') } 59 | Request::Authentication.expects(:new).with('email', 'password', opts).returns(auth_request) 60 | 61 | @session.login('email', 'password', opts) 62 | assert_equal 'toke', @session.auth_token 63 | end 64 | 65 | should "retain the email address for this session" do 66 | Request::Authentication.stubs(:new).returns(stub(:auth_token => 'toke')) 67 | 68 | @session.login('email', 'password') 69 | assert_equal 'email', @session.email 70 | end 71 | 72 | should "know if the Session is for a single user" do 73 | @session.auth_token = "abcdefg1234567" 74 | assert_equal true, @session.single_user? 75 | end 76 | 77 | should "know if the Session is for oauth" do 78 | @session.access_token = 'some_oauth_access_token' 79 | assert_equal true, @session.oauth_user? 80 | end 81 | end 82 | 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /test/unit/garb_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class GarbTest < MiniTest::Unit::TestCase 4 | context "The Garb module" do 5 | should 'prefix a string with ga: for GA' do 6 | assert_equal '-ga:bob', Garb.to_google_analytics(stub(:to_google_analytics => '-ga:bob')) 7 | assert_equal 'ga:bob', Garb.to_google_analytics('bob') 8 | end 9 | 10 | should 'parse out - and put it before ga:' do 11 | assert_equal '-ga:pageviews', Garb.to_google_analytics('-pageviews') 12 | end 13 | 14 | should 'remove ga: prefix' do 15 | assert_equal 'bob', Garb.from_google_analytics('ga:bob') 16 | end 17 | 18 | should "have a helper to parse properties out of entries" do 19 | entry = {"dxp$property"=>[{"name"=>"ga:accountId", "value"=>"1189765"}, {"name"=>"ga:webPropertyId", "value"=>"UA-1189765-1"}]} 20 | 21 | assert_equal({"account_id" => '1189765', "web_property_id" => "UA-1189765-1"}, Garb.parse_properties(entry)) 22 | end 23 | 24 | should "parse out the self link" do 25 | entry = {"link" => [{"rel" => "self", "href" => "http://google.com/accounts/12345"}]} 26 | 27 | assert_equal "http://google.com/accounts/12345", Garb.parse_link(entry, "self") 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /test/unit/symbol_operator_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SymbolOperatorTest < MiniTest::Unit::TestCase 4 | context "An instance of a SymbolOperator" do 5 | should "lower camelize the target" do 6 | assert_equal "ga:uniqueVisits==", SymbolOperator.new(:unique_visits, :eql).to_google_analytics 7 | end 8 | 9 | should "return target and operator together" do 10 | assert_equal "ga:metric==", SymbolOperator.new(:metric, :eql).to_google_analytics 11 | end 12 | 13 | should "prefix the operator to the target" do 14 | assert_equal "-ga:metric", SymbolOperator.new(:metric, :desc).to_google_analytics 15 | end 16 | 17 | # should "know if it is equal to another operator" do 18 | # op1 = SymbolOperator.new(:hello, "==") 19 | # op2 = SymbolOperator.new(:hello, "==") 20 | # assert_equal op1, op2 21 | # end 22 | # 23 | # should "not be equal to another operator if target, operator, or prefix is different" do 24 | # op1 = SymbolOperator.new(:hello, "==") 25 | # op2 = SymbolOperator.new(:hello, "==", true) 26 | # refute_equal op1, op2 27 | # 28 | # op1 = SymbolOperator.new(:hello1, "==") 29 | # op2 = SymbolOperator.new(:hello2, "==") 30 | # refute_equal op1, op2 31 | # 32 | # op1 = SymbolOperator.new(:hello, "!=") 33 | # op2 = SymbolOperator.new(:hello, "==") 34 | # refute_equal op1, op2 35 | # end 36 | end 37 | end 38 | --------------------------------------------------------------------------------