facebook_test_users 0.0.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -3,20 +3,79 @@
3
3
  Testing Facebook apps is hard; part of that difficulty comes from
4
4
  managing your test users. Currently, Facebook's "Developer" app
5
5
  doesn't offer any way to do it, so you wind up with a bunch of `curl`
6
- commands and pain.
6
+ commands and pain (see Facebook's [API
7
+ documentation](https://developers.facebook.com/docs/test_users/) for
8
+ details).
7
9
 
8
10
  This gem tries to take away the pain of managing your test users. It's
9
11
  easy to get started.
10
12
 
11
13
  `$ gem install facebook_test_users`
12
14
 
13
- `$ fbtu apps add --name myapp --app-id 123456 --app-secret abcdef`
15
+ `$ fbtu apps register --name myapp --app-id 123456 --app-secret abcdef`
14
16
 
15
17
  `$ fbtu users list --app myapp`
16
18
 
17
- `$ fbtu users add --app myapp`
19
+ `$ fbtu users create --app myapp --name Fred`
20
+
21
+ `$ fbtu users change --app myapp --user 1000000093284356 --name "Sir Fred"`
22
+
23
+ `$ fbtu apps add-user --from-app myapp --user 1000000093284356 --to-app myotherapp`
24
+
25
+ `$ fbtu apps rm-user --app myapp --user 1000000093284356`
18
26
 
19
27
  `$ fbtu users rm --app myapp --user 1000000093284356`
20
28
 
21
29
  You can also use it in your own Ruby applications; `require
22
30
  "facebook_test_users"` and off you go.
31
+
32
+ ## Integration with Rails apps
33
+
34
+ It's easy to integrate with Rails apps. For example, if your app has
35
+ a `"config/omniauth/#{Rails.env}.yml"` file containing:
36
+
37
+ facebook:
38
+ name: http://localhost:3000
39
+ API_key: 123456789012
40
+ app_secret: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
41
+
42
+ and `config/initializers/omniauth.rb` containing:
43
+
44
+ omniauth_yml_path = Rails.root.join("config", "omniauth", Rails.env + ".yml")
45
+ SETTINGS = YAML.load(IO.read(omniauth_yml_path))
46
+
47
+ Rails.application.config.middleware.use OmniAuth::Builder do
48
+ SETTINGS.each do |service, secrets|
49
+ provider service.to_sym, secrets['API_key'], secrets['app_secret']
50
+ end
51
+ end
52
+
53
+ then you could build simple rake tasks like this:
54
+
55
+ require 'facebook_test_users/cli'
56
+ require File.expand_path "#{Rails.root}/config/initializers/omniauth.rb"
57
+
58
+ fb = SETTINGS['facebook']
59
+ APP_ID = fb['API_key']
60
+ SECRET = fb['app_secret']
61
+
62
+ namespace :fbtu do
63
+ namespace :app do
64
+ desc 'Register facebook app credentials with fbtu'
65
+ task :register do
66
+ FacebookTestUsers::App.create!(:name => fb['name'], :id => APP_ID, :secret => SECRET)
67
+ puts "Registered app '#{fb['name']}'"
68
+ end
69
+ end
70
+
71
+ namespace :users do
72
+ desc 'List test users via fbtu'
73
+ task :list do
74
+ cli = FacebookTestUsers::CLI::Main.start [:users, :list, '--app', fb['name'] ]
75
+ end
76
+ end
77
+ end
78
+
79
+ Then after running `rake fbtu:app:register`, you can invoke `fbtu`
80
+ commands as per normal, using `--app http://localhost:3000` to refer
81
+ to the registered app.
data/bin/fbtu CHANGED
@@ -7,4 +7,4 @@ rescue LoadError
7
7
  require 'facebook_test_users/cli'
8
8
  end
9
9
 
10
- FacebookTestUsers::CLI.start
10
+ FacebookTestUsers::CLI::Main.start
@@ -22,11 +22,13 @@ Gem::Specification.new do |s|
22
22
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
23
  s.require_paths = ["lib"]
24
24
 
25
- s.add_dependency 'rest-client', '~> 1.6.1'
26
- s.add_dependency 'thor', '~> 0.14.6'
27
- s.add_dependency 'json_pure', '~> 1.5.0'
25
+ s.add_dependency 'rest-client', '>= 1.6.1'
26
+ s.add_dependency 'thor', '>= 0.14.6'
27
+ s.add_dependency 'multi_json'
28
+ s.add_dependency 'heredoc_unindent'
28
29
 
29
30
  s.add_development_dependency 'fakeweb', '~>1.3.0'
30
31
  s.add_development_dependency 'fakeweb-matcher', '~>1.2.2'
31
- s.add_development_dependency 'rspec', '~> 2.3.0'
32
+ s.add_development_dependency 'rspec', '>= 2.3.0'
33
+ s.add_development_dependency 'json'
32
34
  end
@@ -1,29 +1,29 @@
1
- require 'json'
1
+ require 'multi_json'
2
2
 
3
3
  module FacebookTestUsers
4
4
  class App
5
5
 
6
6
  attr_reader :name, :id, :secret
7
-
7
+
8
8
  def initialize(attrs)
9
9
  @name, @id, @secret = attrs[:name].to_s, attrs[:id].to_s, attrs[:secret].to_s
10
10
  validate!
11
11
  end
12
-
12
+
13
13
  def attrs
14
14
  {:name => name, :id => id, :secret => secret}
15
15
  end
16
16
 
17
17
  def self.create!(attrs)
18
- new_guy = new(attrs)
18
+ new_app = new(attrs)
19
19
 
20
- if all.find {|app| app.name == new_guy.name }
21
- raise ArgumentError, "App names must be unique, and there is already an app named \"#{new_guy.name}\"."
20
+ if all.find {|app| app.name == new_app.name }
21
+ raise ArgumentError, "App names must be unique, and there is already an app named \"#{new_app.name}\"."
22
22
  end
23
23
 
24
24
  DB.update do |data|
25
25
  data[:apps] ||= []
26
- data[:apps] << new_guy.attrs
26
+ data[:apps] << new_app.attrs
27
27
  end
28
28
  end
29
29
 
@@ -32,17 +32,29 @@ module FacebookTestUsers
32
32
  :access_token => access_token
33
33
  })
34
34
 
35
- JSON[users_data]["data"].map do |user_data|
35
+ MultiJson.decode(users_data)["data"].map do |user_data|
36
36
  User.new(user_data)
37
37
  end
38
38
  end
39
39
 
40
- def create_user
41
- user_data = RestClient.post(users_url,
42
- :access_token => access_token,
43
- :installed => true)
40
+ def create_user(options = {})
41
+ user_data = RestClient.post(users_url, {:access_token => access_token}.merge(options))
42
+ User.new(MultiJson.decode(user_data))
43
+ end
44
+
45
+ def add_user(options)
46
+ raise "add_user called without uid" \
47
+ unless options.has_key?(:uid)
48
+ raise "add_user called without owner_access_token" \
49
+ unless options.has_key?(:owner_access_token)
44
50
 
45
- User.new(JSON[user_data])
51
+ user_data = RestClient.post(users_url, {:access_token => access_token}.merge(options))
52
+ User.new(MultiJson.decode(user_data))
53
+ end
54
+
55
+ def rm_user(uid)
56
+ url = rm_user_url(uid, access_token)
57
+ RestClient.delete(url)
46
58
  end
47
59
 
48
60
  ## query methods
@@ -68,6 +80,10 @@ module FacebookTestUsers
68
80
  GRAPH_API_BASE + "/#{id}/accounts/test-users"
69
81
  end
70
82
 
83
+ def rm_user_url(uid, token)
84
+ users_url + "?uid=#{uid}&access_token=#{URI.escape(token)}"
85
+ end
86
+
71
87
  def validate!
72
88
  unless name && name =~ /\S/
73
89
  raise ArgumentError, "App name must not be empty"
@@ -1,20 +1,53 @@
1
1
  require 'thor'
2
2
  require 'facebook_test_users'
3
+ require 'heredoc_unindent'
3
4
 
4
5
  module FacebookTestUsers
5
- class CLI < Thor
6
- class Apps < Thor
6
+ module CLI
7
+ module Utils
8
+ def find_app!(name)
9
+ app = App.find_by_name(name)
10
+ unless app
11
+ raise Thor::Error, "Unknown app #{name}. Run 'fbtu apps' to see known apps."
12
+ end
13
+ app
14
+ end
15
+
16
+ def bad_request_message(bad_request)
17
+ response = bad_request.response
18
+ json = MultiJson.decode(response)
19
+ json['error']['message'] rescue json.inspect
20
+ end
7
21
 
22
+ def handle_bad_request(raise_error=true)
23
+ begin
24
+ yield
25
+ rescue RestClient::BadRequest => bad_request
26
+ @message = bad_request_message(bad_request)
27
+ raise Thor::Error, "#{bad_request.class}: #@message" if raise_error
28
+ nil
29
+ end
30
+ end
31
+ end
32
+
33
+ class Base < Thor
34
+ include Utils
35
+ end
36
+
37
+ class Apps < Base
8
38
  check_unknown_options!
9
39
  def self.exit_on_failure?() true end
10
40
 
11
- default_task :list
41
+ # default_task currently breaks subcommand help, so it's
42
+ # probably better to leave it out for now:
43
+ # https://github.com/wycats/thor/issues/306
44
+ #default_task :list
12
45
 
13
- desc "add", "Tell fbtu about a new application (must already exist on FB)"
46
+ desc "register", "Tell fbtu about a new application (must already exist on Facebook)"
14
47
  method_option "app_id", :type => :string, :required => true, :banner => "OpenGraph ID of the app"
15
48
  method_option "app_secret", :type => :string, :required => true, :banner => "App's secret key"
16
49
  method_option "name", :type => :string, :required => true, :banner => "Name of the app (so you don't have to remember its ID)"
17
- def add
50
+ def register
18
51
  FacebookTestUsers::App.create!(:name => options[:name], :id => options[:app_id], :secret => options[:app_secret])
19
52
  list
20
53
  end
@@ -26,9 +59,51 @@ module FacebookTestUsers
26
59
  end
27
60
  end
28
61
 
62
+ desc "add-user", "Add an existing user from another app"
63
+ method_option "to_app", :aliases => %w[-t], :type => :string, :required => true,
64
+ :banner => "Name of the application to which user will be added"
65
+ method_option "from_app", :aliases => %w[-f], :type => :string, :required => true,
66
+ :banner => "Name of the application for which user was originally created"
67
+ method_option "user", :aliases => %w[-u], :type => :string, :required => true,
68
+ :banner => "User ID to add"
69
+ method_option "installed", :aliases => %w[-i], :type => :string, :default => true,
70
+ :banner => "Whether your app should be installed for the user"
71
+ method_option "permissions", :aliases => %w[-p], :type => :string, :default => "read_stream",
72
+ :banner => "Permissions the app should be given"
73
+ def add_user
74
+ to_app = find_app!(options[:to_app])
75
+ from_app = find_app!(options[:from_app])
76
+ add_user_options = options.select do |k, v|
77
+ %w[installed permissions].include? k.to_s
78
+ end
79
+ add_user_options[:uid] = options[:user]
80
+ add_user_options[:owner_access_token] = from_app.access_token
81
+ handle_bad_request do
82
+ result = to_app.add_user(add_user_options)
83
+ puts "User #{result.id} added to app '#{options[:to_app]}'"
84
+ end
85
+ end
86
+
87
+ desc "rm-user", "Remove an existing user from an app"
88
+ method_option "app", :type => :string, :required => true,
89
+ :banner => "Name of the application from which user will be removed"
90
+ method_option "user", :aliases => %w[-u], :type => :string, :required => true,
91
+ :banner => "User ID to add"
92
+ def rm_user
93
+ app = find_app!(options[:app])
94
+ result = handle_bad_request do
95
+ app.rm_user(options[:user])
96
+ end
97
+ if result
98
+ puts "User #{options[:user]} removed from app '#{options[:app]}'"
99
+ else
100
+ puts "User #{options[:user]} not removed from app '#{options[:app]}'"
101
+ end
102
+ end
103
+
29
104
  end # Apps
30
105
 
31
- class Users < Thor
106
+ class Users < Base
32
107
  check_unknown_options!
33
108
  def self.exit_on_failure?() true end
34
109
 
@@ -49,32 +124,112 @@ module FacebookTestUsers
49
124
  end
50
125
  end
51
126
 
52
- desc "add", "Add a test user to an application"
53
- method_option "app", :aliases => %w[-a], :type => :string, :required => true, :banner => "Name of the app"
127
+ desc "create", "Create a new test user"
128
+ method_option "app", :aliases => %w[-a], :type => :string, :required => true,
129
+ :banner => "Name of the app"
130
+ method_option "name", :aliases => %w[-n], :type => :string, :required => false,
131
+ :banner => "Name of the new user"
132
+ method_option "installed", :aliases => %w[-i], :type => :string, :required => false,
133
+ :banner => "whether your app should be installed for the test user"
134
+ method_option "locale", :aliases => %w[-l], :type => :string, :required => false,
135
+ :banner => "the locale for the test user"
54
136
 
55
- def add
137
+ def create
56
138
  app = find_app!(options[:app])
57
- user = app.create_user
58
- puts "User ID: #{user.id}"
59
- puts "Access Token: #{user.access_token}"
60
- puts "Login URL: #{user.login_url}"
139
+ attrs = options.select { |k, v| %w(name installed locale).include? k.to_s }
140
+ user = handle_bad_request do
141
+ app.create_user(attrs)
142
+ end
143
+ if user
144
+ puts "User ID: #{user.id}"
145
+ puts "Access Token: #{user.access_token}"
146
+ puts "Login URL: #{user.login_url}"
147
+ puts "Email: #{user.email}"
148
+ puts "Password: #{user.password}"
149
+ end
61
150
  end
62
151
 
63
152
  desc "friend", "Make two of an app's users friends"
64
153
  method_option "app", :aliases => %w[-a], :type => :string, :required => true, :banner => "Name of the app"
65
- method_option "user1", :aliases => %w[-1 -u1], :type => :string, :required => true, :banner => "ID of the first user"
66
- method_option "user2", :aliases => %w[-2 -u2], :type => :string, :required => true, :banner => "ID of the second user"
154
+ method_option "user1", :aliases => %w[-1 -u1], :type => :string, :required => true, :banner => "First user ID"
155
+ method_option "user2", :aliases => %w[-2 -u2], :type => :string, :required => true, :banner => "Second user ID"
67
156
 
68
157
  def friend
69
158
  app = find_app!(options[:app])
70
159
  users = app.users
71
- u1 = users.find {|u| u.id.to_s == options[:user1] } or raise ArgumentError, "No user found w/id #{options[:user1].inspect}"
72
- u2 = users.find {|u| u.id.to_s == options[:user2] } or raise ArgumentError, "No user found w/id #{options[:user2].inspect}"
160
+ u1 = users.find {|u| u.id.to_s == options[:user1] } or \
161
+ raise Thor::Error, "No user found w/id #{options[:user1].inspect}"
162
+ u2 = users.find {|u| u.id.to_s == options[:user2] } or \
163
+ raise Thor::Error, "No user found w/id #{options[:user2].inspect}"
164
+
165
+ # The first request is just a request; the second request
166
+ # accepts the first request.
167
+ handle_bad_request do
168
+ u1.send_friend_request_to(u2)
169
+ u2.send_friend_request_to(u1)
170
+ end
171
+ end
73
172
 
74
- # the first request is just a request; the second request
75
- # accepts the first request
76
- u1.send_friend_request_to(u2)
77
- u2.send_friend_request_to(u1)
173
+ desc "change", "Change a test user's name and/or password"
174
+ method_option "app", :aliases => %w[-a], :type => :string, :required => true,
175
+ :banner => "Name of the app"
176
+ method_option "user", :aliases => %w[-u], :type => :string, :required => true,
177
+ :banner => "ID of the user to change"
178
+ method_option "name", :aliases => %w[-n], :type => :string, :required => false,
179
+ :banner => "New name for the user"
180
+ method_option "password", :aliases => %w[-n], :type => :string, :required => false,
181
+ :banner => "New password for the user"
182
+
183
+ def change
184
+ app = find_app!(options[:app])
185
+ user = app.users.find do |user|
186
+ user.id.to_s == options[:user].to_s
187
+ end
188
+
189
+ if user
190
+ response = handle_bad_request do
191
+ user.change(options)
192
+ end
193
+ if response == "true"
194
+ puts "Successfully changed user"
195
+ else
196
+ puts "Failed to change user"
197
+ end
198
+ else
199
+ raise Thor::Error, "Unknown user '#{options[:user]}'"
200
+ end
201
+ end
202
+
203
+ desc "list-apps", "List apps associated with the user"
204
+ method_option "user", :aliases => %w[-u], :type => :string, :required => true,
205
+ :banner => "ID of the user for which to list associated apps"
206
+ method_option "app", :aliases => %w[-a], :type => :string, :required => true,
207
+ :banner => "Name of the app owning the user"
208
+ def list_apps
209
+ app = find_app!(options[:app])
210
+ user = app.users.find do |user|
211
+ user.id.to_s == options[:user].to_s
212
+ end
213
+
214
+ if user
215
+ response = handle_bad_request do
216
+ user.owner_apps(app)
217
+ end
218
+ if response
219
+ json = MultiJson.decode(response)
220
+ apps = json['data'] rescue nil
221
+ if apps
222
+ shell.print_table([
223
+ ['App name', 'App ID'],
224
+ *(apps.map { |app| [app['name'], app['id']] })
225
+ ])
226
+ else
227
+ $stderr.write("No apps returned; response was: #{response}\n")
228
+ end
229
+ end
230
+ else
231
+ raise Thor::Error, "Unknown user '#{options[:user]}'"
232
+ end
78
233
  end
79
234
 
80
235
  desc "rm", "Remove a test user from an application"
@@ -88,10 +243,32 @@ module FacebookTestUsers
88
243
  end
89
244
 
90
245
  if user
91
- user.destroy
246
+ result = handle_bad_request(raise_error=false) do
247
+ user.destroy
248
+ end
249
+ if result
250
+ puts "User ID #{user.id} removed"
251
+ else
252
+ if @message.match /(\(#2903\) Cannot delete this test account because it is associated with other applications.)/
253
+ error = <<-EOMSG.unindent.gsub(/^\|/, '')
254
+ #$1
255
+ Run:
256
+ |
257
+ fbtu users list-apps --app #{options[:app]} --user #{user.id}
258
+ |
259
+ then for each of the other apps, run:
260
+ |
261
+ fbtu apps rm-user --app APP-NAME --user #{user.id}
262
+ |
263
+ Then re-run this command.
264
+ EOMSG
265
+ else
266
+ error = @message
267
+ end
268
+ raise Thor::Error, error
269
+ end
92
270
  else
93
- $stderr.write("Unknown user '#{options[:user]}'")
94
- raise ArgumentError, "No such user"
271
+ raise Thor::Error, "Unknown user '#{options[:user]}'"
95
272
  end
96
273
  end
97
274
 
@@ -103,26 +280,17 @@ module FacebookTestUsers
103
280
  app.users.each(&:destroy)
104
281
  end
105
282
 
106
- private
107
- def find_app!(name)
108
- app = App.find_by_name(options[:app])
109
- unless app
110
- $stderr.puts "Unknown app #{options[:app]}."
111
- $stderr.puts "Run 'fbtu apps' to see known apps."
112
- raise ArgumentError, "No such app"
113
- end
114
- app
115
- end
116
-
117
283
  end # Users
118
284
 
119
- check_unknown_options!
120
- def self.exit_on_failure?() true end
285
+ class Main < Thor
286
+ check_unknown_options!
287
+ def self.exit_on_failure?() true end
121
288
 
122
- desc "apps", "Commands for managing FB applications"
123
- subcommand :apps, FacebookTestUsers::CLI::Apps
289
+ desc "apps", "Commands for managing FB applications"
290
+ subcommand :apps, Apps
124
291
 
125
- desc "apps", "Commands for managing FB applications' test users"
126
- subcommand :users, FacebookTestUsers::CLI::Users
292
+ desc "users", "Commands for managing FB applications test users"
293
+ subcommand :users, Users
294
+ end
127
295
  end
128
296
  end