diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/helpers/bonafide_helper.rb | 99 | ||||
-rw-r--r-- | tests/helpers/http_helper.rb | 4 | ||||
-rw-r--r-- | tests/helpers/os_helper.rb | 8 | ||||
-rwxr-xr-x | tests/helpers/soledad_sync.py | 78 | ||||
-rw-r--r-- | tests/helpers/srp_helper.rb | 8 | ||||
-rw-r--r-- | tests/white-box/couchdb.rb | 2 | ||||
-rw-r--r-- | tests/white-box/webapp.rb | 103 |
7 files changed, 237 insertions, 65 deletions
diff --git a/tests/helpers/bonafide_helper.rb b/tests/helpers/bonafide_helper.rb new file mode 100644 index 00000000..9b26eaaf --- /dev/null +++ b/tests/helpers/bonafide_helper.rb @@ -0,0 +1,99 @@ +# +# helper for the communication with the provider API for creating, authenticating, and deleting accounts. +# + +class LeapTest + + def assert_tmp_user + user = assert_create_user + assert_authenticate_user(user) + yield user if block_given? + assert_delete_user(user) + rescue StandardError, MiniTest::Assertion => exc + begin + assert_delete_user(user) + rescue + end + raise exc + end + + def api_url(path) + api = property('api') + "https://%{domain}:%{port}#{path}" % { + :domain => api['domain'], + :port => api['port'] + } + end + + # + # attempts to create a user account via the API, + # returning the user object if successful. + # + def assert_create_user + user = SRP::User.new + url = api_url("/1/users.json") + assert_post(url, user.to_params) do |body| + assert response = JSON.parse(body), 'response should be JSON' + assert response['ok'], "Creating a user should be successful, got #{response.inspect} instead." + end + user.ok = true + return user + end + + # + # attempts to authenticate user. if successful, + # user object is updated with id and session token. + # + def assert_authenticate_user(user) + url = api_url("/1/sessions.json") + session = SRP::Session.new(user) + params = {'login' => user.username, 'A' => session.aa} + assert_post(url, params) do |response, body| + cookie = response['Set-Cookie'].split(';').first + assert(response = JSON.parse(body), 'response should be JSON') + assert(session.bb = response["B"], 'response should include "B"') + url = api_url("/1/sessions/login.json") + params = {'client_auth' => session.m, 'A' => session.aa} + options = {:headers => {'Cookie' => cookie}} + assert_put(url, params, options) do |body| + assert(response = JSON.parse(body), 'response should be JSON') + assert(response['M2'], 'response should include M2') + user.session_token = response['token'] + user.id = response['id'] + assert(user.session_token, 'response should include token') + assert(user.id, 'response should include user id') + end + end + end + + # + # attempts to destroy a user account via the API. + # + def assert_delete_user(user) + if user && user.ok && user.id && user.session_token && !user.deleted + url = api_url("/1/users/#{user.id}.json") + options = {:headers => { + "Authorization" => "Token token=\"#{user.session_token}\"" + }} + params = { + :identities => 'destroy' + } + user.deleted = true + delete(url, params, options) do |body, response, error| + assert error.nil?, "Error deleting user: #{error}" + assert response.code.to_i == 200, "Unable to delete user: HTTP response from API should have code 200, was #{response.code} #{error} #{body}" + assert(response = JSON.parse(body), 'Delete response should be JSON') + assert(response["success"], 'Deleting user should be a success') + end + domain = property('domain.full_suffix') + identities_url = couchdb_url("/identities/_design/Identity/_view/by_address?key=%22#{user.username}@#{domain}%22") + get(identities_url) do |body, response, error| + assert error.nil?, "Error checking identities db: #{error}" + assert response.code.to_i == 200, "Unable to check that user identity was deleted: HTTP response from API should have code 200, was #{response.code} #{error} #{body}" + assert(response = JSON.parse(body), 'Couch response should be JSON') + assert response['rows'].empty?, "Identity should have been deleted for test user #{user.username} (id #{user.id}), but was not! Response was: #{body}." + end + end + end + +end diff --git a/tests/helpers/http_helper.rb b/tests/helpers/http_helper.rb index c941ef63..0b13b754 100644 --- a/tests/helpers/http_helper.rb +++ b/tests/helpers/http_helper.rb @@ -93,9 +93,9 @@ class LeapTest yield(response, body) if block.arity == 2 end elsif response - fail ["Expected a 200 status code from #{url}, but got #{response.code} instead.", error_msg, body].compact.join("\n") + fail ["Expected a 200 status code from #{method} #{url}, but got #{response.code} instead.", error_msg, body].compact.join("\n") else - fail ["Expected a response from #{url}, but got \"#{error}\" instead.", error_msg, body].compact.join("\n"), error + fail ["Expected a response from #{method} #{url}, but got \"#{error}\" instead.", error_msg, body].compact.join("\n"), error end end end diff --git a/tests/helpers/os_helper.rb b/tests/helpers/os_helper.rb index 529e899f..aad67dda 100644 --- a/tests/helpers/os_helper.rb +++ b/tests/helpers/os_helper.rb @@ -17,8 +17,12 @@ class LeapTest }.compact end - def assert_running(process) - assert pgrep(process).any?, "No running process for #{process}" + def assert_running(process, options={}) + processes = pgrep(process) + assert processes.any?, "No running process for #{process}" + if options[:single] + assert processes.length == 1, "More than one process for #{process}" + end end # diff --git a/tests/helpers/soledad_sync.py b/tests/helpers/soledad_sync.py new file mode 100755 index 00000000..2fb865fc --- /dev/null +++ b/tests/helpers/soledad_sync.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python + +# +# Test Soledad sync +# +# This script performs a slightly modified U1DB sync to the Soledad server and +# returns whether that sync was successful or not. +# +# It takes three arguments: +# +# uuid -- uuid of the user to sync +# token -- a valid session token +# server -- the url of the soledad server we should connect to +# +# For example: +# +# soledad_sync.py f6bef0586fcfdb8705e26a58f2d9e580 uYO-4ucEJFksJ6afjmcYwIyap2vW7bv6uLxk0w_RfCc https://199.119.112.9:2323/user-f6bef0586fcfdb8705e26a58f2d9e580 +# + +import os +import sys +import traceback +import tempfile +import shutil +import u1db + +from u1db.remote.http_target import HTTPSyncTarget + +# +# monkey patch U1DB's HTTPSyncTarget to perform token based auth +# + +def set_token_credentials(self, uuid, token): + self._creds = {'token': (uuid, token)} + +def _sign_request(self, method, url_query, params): + uuid, token = self._creds['token'] + auth = '%s:%s' % (uuid, token) + return [('Authorization', 'Token %s' % auth.encode('base64')[:-1])] + +HTTPSyncTarget.set_token_credentials = set_token_credentials +HTTPSyncTarget._sign_request = _sign_request + +# +# Create a temporary local u1db replica and attempt to sync to it. +# Returns a failure message if something went wrong. +# + +def soledad_sync(uuid, token, server): + tempdir = tempfile.mkdtemp() + try: + db = u1db.open(os.path.join(tempdir, '%s.db' % uuid), True) + creds = {'token': {'uuid': uuid, 'token': token}} + db.sync(server, creds=creds, autocreate=False) + finally: + shutil.rmtree(tempdir) + +# +# exit codes: +# +# 0 - OK +# 1 - WARNING +# 2 - ERROR +# + +if __name__ == '__main__': + try: + uuid, token, server = sys.argv[1:] + result = soledad_sync(uuid, token, server) + if result is None: + exit(0) + else: + print(result) + exit(1) + except Exception as exc: + print(exc.message or str(exc)) + traceback.print_exc(file=sys.stdout) + exit(2) diff --git a/tests/helpers/srp_helper.rb b/tests/helpers/srp_helper.rb index 9f4d7f5b..5d30b459 100644 --- a/tests/helpers/srp_helper.rb +++ b/tests/helpers/srp_helper.rb @@ -6,6 +6,7 @@ require 'digest' require 'openssl' require 'securerandom' +require 'base64' module SRP @@ -135,16 +136,15 @@ d15dc7d7b46154d6b6ce8ef4ad69b15d4982559b297bcf1885c529f566660e5 class User include SRP::Util - attr_accessor :username - attr_accessor :password - attr_accessor :salt - attr_accessor :verifier + attr_accessor :username, :password, :salt, :verifier, :id, :session_token, :ok, :deleted def initialize @username = "test_user_" + SecureRandom.urlsafe_base64(10).downcase.gsub(/[_-]/, '') @password = "password_" + SecureRandom.urlsafe_base64(10) @salt = bigrand(4).hex @verifier = modpow(GENERATOR, private_key) + @ok = false + @deleted = false end def private_key diff --git a/tests/white-box/couchdb.rb b/tests/white-box/couchdb.rb index 2788f4f7..450c4201 100644 --- a/tests/white-box/couchdb.rb +++ b/tests/white-box/couchdb.rb @@ -9,7 +9,7 @@ class CouchDB < LeapTest end def test_00_Are_daemons_running? - assert_running 'tapicero' + assert_running '^tapicero', :single => true if multimaster? assert_running 'bin/beam' assert_running 'bin/epmd' diff --git a/tests/white-box/webapp.rb b/tests/white-box/webapp.rb index 2aa87403..9f104899 100644 --- a/tests/white-box/webapp.rb +++ b/tests/white-box/webapp.rb @@ -41,63 +41,26 @@ class Webapp < LeapTest pass end - def test_05_Can_create_user? - @@user = nil - user = SRP::User.new - url = api_url("/1/users.json") - assert_post(url, user.to_params) do |body| - assert response = JSON.parse(body), 'response should be JSON' - assert response['ok'], 'creating a user should be successful' - end - @@user = user + def test_05_Can_create_and_authenticate_and_delete_user_via_API? + assert_tmp_user pass end - def test_06_Can_authenticate? - @@user_id = nil - @@session_token = nil - if @@user.nil? - skip "Depends on user creation" - else - url = api_url("/1/sessions.json") - session = SRP::Session.new(@@user) - params = {'login' => @@user.username, 'A' => session.aa} - assert_post(url, params) do |response, body| - cookie = response['Set-Cookie'].split(';').first - assert(response = JSON.parse(body), 'response should be JSON') - assert(bb = response["B"]) - session.bb = bb - url = api_url("/1/sessions/login.json") - params = {'client_auth' => session.m, 'A' => session.aa} - options = {:headers => {'Cookie' => cookie}} - assert_put(url, params, options) do |body| - assert(response = JSON.parse(body), 'response should be JSON') - assert(response['M2'], 'response should include M2') - assert(@@session_token = response['token'], 'response should include token') - assert(@@user_id = response['id'], 'response should include user id') - end - end - pass - end - end - - def test_07_Can_delete_user? - if @@user_id.nil? || @@session_token.nil? - skip "Depends on authentication" - else - url = api_url("/1/users/#{@@user_id}.json") - options = {:headers => { - "Authorization" => "Token token=\"#{@@session_token}\"" - }} - delete(url, {}, options) do |body, response, error| - if response.code.to_i != 200 - skip "It appears the web api is too old to support deleting users" - else - assert(response = JSON.parse(body), 'response should be JSON') - assert(response["success"], 'delete should be a success') + def test_06_Can_sync_Soledad? + soledad_config = property('definition_files.soledad_service') + if soledad_config && !soledad_config.empty? + soledad_server = pick_soledad_server(soledad_config) + if soledad_server + assert_tmp_user do |user| + assert_user_db_exists(user) + command = File.expand_path "../../helpers/soledad_sync.py", __FILE__ + soledad_url = "https://#{soledad_server}/user-#{user.id}" + assert_run "#{command} #{user.id} #{user.session_token} #{soledad_url}" pass end end + else + skip 'No soledad service configuration' end end @@ -110,11 +73,39 @@ class Webapp < LeapTest } end - def api_url(path) - "https://%{domain}:%{port}#{path}" % { - :domain => property('api.domain'), - :port => property('api.port') - } + # + # pick a random soledad server. + # I am not sure why, but using IP address directly does not work. + # + def pick_soledad_server(soledad_config_json_str) + soledad_config = JSON.parse(soledad_config_json_str) + host_name = soledad_config['hosts'].keys.shuffle.first + if host_name + hostname = soledad_config['hosts'][host_name]['hostname'] + port = soledad_config['hosts'][host_name]['port'] + return "#{hostname}:#{port}" + else + return nil + end + end + + # + # returns true if the per-user db created by tapicero exists. + # we try three times, and give up after that. + # + def assert_user_db_exists(user) + last_body, last_response, last_error = nil + 3.times do + sleep 0.1 + get(couchdb_url("/user-#{user.id}/_design/docs")) do |body, response, error| + last_body, last_response, last_error = body, response, error + if response.code.to_i == 200 + return + end + end + sleep 0.2 + end + assert false, "Could not find user db for test user #{user.username}\nuuid=#{user.id}\nHTTP #{last_response.code} #{last_error} #{last_body}" end # |