facebook_test_users 0.0.2 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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