This code works with the Firebase simple-auth interface. It depends on the following Gemfile:
gem 'ruby_motion_query', :git => '[email protected]:infinitered/rmq.git'
gem 'motion-firebase'
'motion-firebase',
'motion-firebase-auth',
]
gem 'motion-map'
gem 'motion-callback'
gem 'motion-stump'
gem 'bacon-expect'
gem 'motion-redgreen'
gem 'guard'
gem 'guard-test'
gem 'motion-cocoapods'
A bit about the motion-callback stuff. What happens here is that the calling method looks a bit like this:
login_from_keychain do |on|
on.success {
# take success action
}
on.failure {|status|
# take failure action
}
end
Note that we don't have a clue how motion-firebase or, indeed, Firebase will communicate or how long they will take. It's not feasible to provide a WebMock to fake the responses out because we don't know what the right responses are. I suspect Firebase uses sockets anyhow.
One could make that case that the specs are trying to reach too deep and test code that is not in the business domain. I would argue the contrary: Authorization, creation and removal of users, etc. are in the business domain and they have to brush up against some foreign interface (in this case keychain and motion-firebase) to accomplish their goals.
I've deliberately left the specs blank to see what people come up with. If there's something boneheaded in the code, I'm happy to hear about it, but the point is that this code, empirically, works and with tests I can refactor it with more confidence.
describe "user" do
describe "logging in" do
it "logs in from keychain if there are keychain credentials" do
end
it "successfully logs in from correct user id and password" do
end
it "fails to log in from incorrect user id or password" do
end
it "creates a user successfully" do
end
it "politely logs the user in after successfully creating him/her" do
end
end
end
and the User code
class User
@@auth = nil
@@logged_in = false
@@keychain = nil
class << self
def check(&block)
auth ||= rmq.app.delegate.auth
status = blank_status
auth.check{|error, user|
status.error = error
status.user = user
if error.nil? && user.respond_to?(:email)
@@logged_in = status.logged_in = error.nil?
block.call Callback.new(:success, status)
else
block.call Callback.new(:failure, status)
end
}
end
def logged_in?
@@logged_in
end
def login_from_keychain(&block)
block.call Callback.new(:failure) unless Persistence[:remember_me]
@@keychain ||= KeychainItemWrapper.alloc.initWithIdentifier 'Supercharger', accessGroup: nil
username = @@keychain.objectForKey KSecAttrAccount
password = @@keychain.objectForKey KSecValueData
login(username, password) do |status|
if status.error == 'success'
status.logged_in = true
block.call Callback.new(:success)
else
block.call Callback.new(:failure)
end
end
end
def add_to_keychain(email, password)
@@keychain.setObject email, forKey: KSecAttrAccount
@@keychain.setObject password, forKey: KSecValueData
end
def remove_from_keychain
@@keychain ||= KeychainItemWrapper.alloc.initWithIdentifier 'Supercharger', accessGroup: nil
@@keychain.setObject nil, forKey: KSecAttrAccount
@@keychain.setObject nil, forKey: KSecValueData
end
def login(email, password, &block)
status = blank_status
auth.login(email: email, password: password) { |error, user|
status.user = user
if error.nil?
if user.nil?
status.error = "no error but user is nil"
end
block.call(status)
else
status.error = error.localizedDescription
block.call(status)
end
}
end
def create(email, password, &block)
credentials = {
email:email,
password:password
}
rmq.app.delegate.auth.create(credentials) do |error, user|
if error
block.call Callback.new(:failure, error.localizedDescription)
else
block.call Callback.new(:success, error)
end
end
end
def remove(email, password)
rmq.app.delegate.auth.remove(email: email, password: password){|error, user|
}
end
private
def auth
@@auth ||= rmq.app.delegate.auth
end
def blank_status
MotionMap::Map.new({error: 'success', user: nil, logged_in: false})
end
end
end