summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/factories.rb43
-rw-r--r--test/files/ca.crt15
-rw-r--r--test/files/ca.key16
-rw-r--r--test/functional/application_controller_test.rb28
-rw-r--r--test/functional/helper_methods_test.rb39
-rw-r--r--test/functional/keys_controller_test.rb32
-rw-r--r--test/functional/sessions_controller_test.rb59
-rw-r--r--test/functional/test_helpers_test.rb38
-rw-r--r--test/functional/users_controller_test.rb165
-rw-r--r--test/functional/v1/certs_controller_test.rb46
-rw-r--r--test/functional/v1/messages_controller_test.rb57
-rw-r--r--test/functional/v1/services_controller_test.rb29
-rw-r--r--test/functional/v1/sessions_controller_test.rb62
-rw-r--r--test/functional/v1/users_controller_test.rb74
-rw-r--r--test/functional/webfinger_controller_test.rb33
-rw-r--r--test/integration/api/Readme.md23
-rw-r--r--test/integration/api/login_test.rb50
-rw-r--r--test/integration/api/pgp_key_test.rb35
-rwxr-xr-xtest/integration/api/python/flow_with_srp.py96
-rwxr-xr-xtest/integration/api/python/login_wrong_username.py19
-rwxr-xr-xtest/integration/api/python/signup.py20
-rwxr-xr-xtest/integration/api/python/signup_and_login.py44
-rwxr-xr-xtest/integration/api/python/signup_and_login_wrong_password.py43
-rwxr-xr-xtest/integration/api/python/umlauts.py79
-rw-r--r--test/integration/api/signup_test.rb20
-rw-r--r--test/integration/api/srp_test.rb105
-rw-r--r--test/integration/api/update_account_test.rb51
-rw-r--r--test/integration/browser/account_test.rb164
-rw-r--r--test/integration/browser/password_validation_test.rb31
-rw-r--r--test/integration/browser/session_test.rb17
-rw-r--r--test/integration/navigation_test.rb9
-rw-r--r--test/integration/os_detection_test.rb20
-rw-r--r--test/leap_web_users_test.rb7
-rwxr-xr-xtest/nagios/soledad_sync.py95
-rw-r--r--test/nagios/support/__init__.py0
-rw-r--r--test/nagios/support/api.py39
-rw-r--r--test/nagios/support/config.py14
-rw-r--r--test/nagios/support/nagios_report.py24
-rw-r--r--test/nagios/support/nagios_test.py49
-rw-r--r--test/nagios/support/user.py64
-rwxr-xr-xtest/nagios/webapp_login.py90
-rwxr-xr-xtest/nagios/webapp_signup.py19
-rw-r--r--test/support/assert_responses.rb46
-rw-r--r--test/support/auth_test_helper.rb65
-rw-r--r--test/support/browser_integration_test.rb98
-rw-r--r--test/support/rack_test.rb38
-rw-r--r--test/support/stub_record_helper.rb53
-rw-r--r--test/support/time_test_helper.rb30
-rw-r--r--test/support/with_config_helper.rb16
-rw-r--r--test/test_helper.rb78
-rw-r--r--test/unit/account_test.rb47
-rw-r--r--test/unit/anonymous_user_test.rb23
-rw-r--r--test/unit/client_certificate_test.rb24
-rw-r--r--test/unit/helpers/session_helper_test.rb4
-rw-r--r--test/unit/helpers/users_helper_test.rb4
-rw-r--r--test/unit/identity_test.rb133
-rw-r--r--test/unit/local_email_test.rb65
-rw-r--r--test/unit/token_test.rb89
-rw-r--r--test/unit/user_test.rb68
-rw-r--r--test/unit/warden_strategy_secure_remote_password_test.rb63
-rw-r--r--test/unit/webfinger/host_meta_presenter_test.rb24
-rw-r--r--test/unit/webfinger/user_presenter_test.rb49
62 files changed, 2755 insertions, 225 deletions
diff --git a/test/factories.rb b/test/factories.rb
index 6c671f8..bebda5c 100644
--- a/test/factories.rb
+++ b/test/factories.rb
@@ -1,3 +1,44 @@
-Dir.glob(Rails.root.join('**','test','factories.rb')) do |factory_file|
+ENGINE_FACTORY_FILES = Rails.root.join('engines','*','test','factories.rb')
+Dir.glob(ENGINE_FACTORY_FILES) do |factory_file|
require factory_file
end
+
+FactoryGirl.define do
+
+ factory :user do
+ login { Faker::Internet.user_name }
+ password_verifier "1234ABCD"
+ password_salt "4321AB"
+
+ factory :user_with_settings do
+ email_forward { Faker::Internet.email }
+ email_aliases_attributes do
+ {:a => Faker::Internet.user_name + '@' + APP_CONFIG[:domain]}
+ end
+ end
+
+ factory :admin_user do
+ after(:build) do |admin|
+ admin.stubs(:is_admin?).returns(true)
+ end
+ end
+
+ factory :premium_user do
+ effective_service_level_code 2
+ end
+
+ end
+
+ factory :token do
+ user
+ end
+
+ factory :pgp_key do
+ keyblock <<-EOPGP
+-----BEGIN PGP PUBLIC KEY BLOCK-----
++Dummy+PGP+KEY+++Dummy+PGP+KEY+++Dummy+PGP+KEY+++Dummy+PGP+KEY+
+#{SecureRandom.base64(4032)}
+-----END PGP PUBLIC KEY BLOCK-----
+ EOPGP
+ end
+end
diff --git a/test/files/ca.crt b/test/files/ca.crt
new file mode 100644
index 0000000..8393eee
--- /dev/null
+++ b/test/files/ca.crt
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICYDCCAcCgAwIBAgIBATANBgkqhkiG9w0BAQ0FADA7MREwDwYDVQQKDAh0ZXN0
+IG9yZzESMBAGA1UECwwJdGVzdCB1bml0MRIwEAYDVQQDDAl0ZXN0IG5hbWUwIBcN
+MTMwMjA1MDAwMDAwWhgPMjExMzAyMDUwMDAwMDBaMDsxETAPBgNVBAoMCHRlc3Qg
+b3JnMRIwEAYDVQQLDAl0ZXN0IHVuaXQxEjAQBgNVBAMMCXRlc3QgbmFtZTCBqDAN
+BgkqhkiG9w0BAQEFAAOBlgAwgZICgYoAx076Dz8zswvCLuz0HP3Y3PWOgFDo9+8o
+H4uXRcTpd+yw+5B79xjtQ7ojQy2465Jq00nkzHI6V1otM2uvVVIOcNk0t1HEjmK0
+T/r96dDHc59YvVQ+XPrzuQ4t3iREy8IAPNbc3r29PVZkMdGpeSYxyY1mUKza4DcY
+My4SVko9pcP8zJBD4bHgEa0CAwEAAaNgMF4wHQYDVR0OBBYEFOQ+d2EUwBpi93TJ
+9AX4Okew5/UIMA4GA1UdDwEB/wQEAwICBDAMBgNVHRMEBTADAQH/MB8GA1UdIwQY
+MBaAFOQ+d2EUwBpi93TJ9AX4Okew5/UIMA0GCSqGSIb3DQEBDQUAA4GKAJW9/39P
+VbVjH9C7F0XMOpd9nWBe9NUoiw36ZFZw95dqfUm6j5f3nejWG4lEtyMFu5i5rAw6
+GdDSXmq4sUqWTaJmQmZyY+WggQR4UGWJ0I18HRDiPxuA++OfkGzA20Gmvk+CIw/J
+QLHlVjLyyUwaA+EO88rEcdc9VnGL/Xgjh8C/PYH2DpWw/kJa
+-----END CERTIFICATE-----
diff --git a/test/files/ca.key b/test/files/ca.key
new file mode 100644
index 0000000..125997f
--- /dev/null
+++ b/test/files/ca.key
@@ -0,0 +1,16 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIChAIBAAKBigDHTvoPPzOzC8Iu7PQc/djc9Y6AUOj37ygfi5dFxOl37LD7kHv3
+GO1DuiNDLbjrkmrTSeTMcjpXWi0za69VUg5w2TS3UcSOYrRP+v3p0Mdzn1i9VD5c
++vO5Di3eJETLwgA81tzevb09VmQx0al5JjHJjWZQrNrgNxgzLhJWSj2lw/zMkEPh
+seARrQIDAQABAoGJIvn0HircOsaMfEmvCUtu/E/HgzMvvxrkMqz/jgnhYt9Rq8QO
+TS29rY4D1C0473ZRcuTb1xkQrfWwSv7R1SpCSIGFo8obtGb0NjNaYGyQ0IrYDjk8
+H5kYFEY4X4oqFhgy3owewaZZLxLD336ARRj2HhsLzA+4nD/wF7Q+bggpuMdkM2Uj
+tn12rIECRQ/XqIGF8jLw9IDMkr9kkfT+n03p8sOd4g7iSw0sknlzaZZpIDvibkyN
+SDKM7VX4VQa7u58+sCF4ylwi0UQu7/VT7Smp4QJFDJSoEOKplBvaT9fTfdVKjE4P
+QyCAWEsb6Up8KKswhtDqiWeFtktIvx1Mkxn25erLms3cUEBde//rwNB+6ItBR/N8
+4RlNAkUPLsc3Gn+7gmFQ7r3U3zViboON0B/wiWcUjJsQzR6zdoBCvg0+VwsOIniG
+ubjbI1uZUGHHg/SYn4KQOm4DwlgF7aDkxQECRQjVZMEedlXxzLOdZvoHBuZHdT38
+F0Jn0rxXOaDQuy0eimBamS+r4vOWngr4Az3jRH15KMYMu9dyllX3z/R2uyrLVBc2
+TQJFBEHIjoMVgP2h+N6VUDgPOhnxnnLvowOtX23J1y2foKwfZrHH38LNcWmuaGUi
+fz6EYeUO20D174GfhqB0j6yR50ejPjYD
+-----END RSA PRIVATE KEY-----
diff --git a/test/functional/application_controller_test.rb b/test/functional/application_controller_test.rb
new file mode 100644
index 0000000..c4c922b
--- /dev/null
+++ b/test/functional/application_controller_test.rb
@@ -0,0 +1,28 @@
+require 'test_helper'
+
+class ApplicationControllerTest < ActionController::TestCase
+
+ def setup
+ # so we can test the effect on the response
+ @controller.response = @response
+ end
+
+ def test_require_login_redirect
+ @controller.send(:require_login)
+ assert_access_denied(true, false)
+ end
+
+ def test_require_login
+ login
+ @controller.send(:require_login)
+ assert_access_denied(false)
+ end
+
+ def test_require_admin
+ login
+ @current_user.expects(:is_admin?).returns(false)
+ @controller.send(:require_admin)
+ assert_access_denied
+ end
+
+end
diff --git a/test/functional/helper_methods_test.rb b/test/functional/helper_methods_test.rb
new file mode 100644
index 0000000..44226ae
--- /dev/null
+++ b/test/functional/helper_methods_test.rb
@@ -0,0 +1,39 @@
+#
+# Testing and documenting the helper methods available from
+# ApplicationController
+#
+
+require 'test_helper'
+
+class HelperMethodsTest < ActionController::TestCase
+ tests ApplicationController
+
+ # we test them right in here...
+ include ApplicationController._helpers
+
+ # the helpers all reference the controller.
+ def controller
+ @controller
+ end
+
+ def test_current_user
+ login
+ assert_equal @current_user, current_user
+ end
+
+ def test_logged_in
+ login
+ assert logged_in?
+ end
+
+ def test_logged_out
+ assert !logged_in?
+ end
+
+ def test_admin
+ login
+ @current_user.expects(:is_admin?).returns(bool = stub)
+ assert_equal bool, admin?
+ end
+
+end
diff --git a/test/functional/keys_controller_test.rb b/test/functional/keys_controller_test.rb
new file mode 100644
index 0000000..863be93
--- /dev/null
+++ b/test/functional/keys_controller_test.rb
@@ -0,0 +1,32 @@
+require 'test_helper'
+
+class KeysControllerTest < ActionController::TestCase
+
+ test "get existing public key" do
+ public_key = 'my public key'
+ @user = stub_record :user, :public_key => public_key
+ User.stubs(:find_by_login).with(@user.login).returns(@user)
+ get :show, :login => @user.login
+ assert_response :success
+ assert_equal "text/text", response.content_type
+ assert_equal public_key, response.body
+ end
+
+ test "get non-existing public key for user" do
+ # this isn't a scenerio that should generally occur.
+ @user = stub_record :user
+ User.stubs(:find_by_login).with(@user.login).returns(@user)
+ get :show, :login => @user.login
+ assert_response :success
+ assert_equal "text/text", response.content_type
+ assert_equal '', response.body.strip
+ end
+
+ test "get public key for non-existing user" do
+ # raise 404 error if user doesn't exist (doesn't need to be this routing error, but seems fine to assume for now):
+ assert_raise(ActionController::RoutingError) {
+ get :show, :login => 'asdkljslksjfdlskfj'
+ }
+ end
+
+end
diff --git a/test/functional/sessions_controller_test.rb b/test/functional/sessions_controller_test.rb
new file mode 100644
index 0000000..fe7903f
--- /dev/null
+++ b/test/functional/sessions_controller_test.rb
@@ -0,0 +1,59 @@
+require 'test_helper'
+
+# This is a simple controller unit test.
+# We're stubbing out both warden and srp.
+# There's an integration test testing the full rack stack and srp
+class SessionsControllerTest < ActionController::TestCase
+
+ setup do
+ @user = stub :login => "me", :id => 123
+ @client_hex = 'a123'
+ end
+
+ test "should get login screen" do
+ get :new
+ assert_response :success
+ assert_equal "text/html", response.content_type
+ assert_template "sessions/new"
+ end
+
+ test "redirect to home_url if logged in" do
+ login
+ get :new
+ assert_response :redirect
+ assert_redirected_to home_url
+ end
+
+ test "renders json" do
+ get :new, :format => :json
+ assert_response :success
+ assert_json_error nil
+ end
+
+ test "renders warden errors" do
+ request.env['warden.options'] = {attempted_path: '/1/sessions/asdf.json'}
+ strategy = stub :message => {:field => :translate_me}
+ request.env['warden'].stubs(:winning_strategy).returns(strategy)
+ I18n.expects(:t).with(:translate_me).at_least_once.returns("translation stub")
+ get :new, :format => :json
+ assert_response 422
+ assert_json_error :field => "translation stub"
+ end
+
+ test "renders failed attempt message" do
+ request.env['warden.options'] = {attempted_path: '/1/sessions/asdf.json'}
+ request.env['warden'].stubs(:winning_strategy).returns(nil)
+ get :new, :format => :json
+ assert_response 422
+ assert_json_error :login => I18n.t(:all_strategies_failed)
+ end
+
+ test "destory should logout" do
+ login
+ expect_logout
+ delete :destroy
+ assert_response :redirect
+ assert_redirected_to home_url
+ end
+
+end
diff --git a/test/functional/test_helpers_test.rb b/test/functional/test_helpers_test.rb
new file mode 100644
index 0000000..845e516
--- /dev/null
+++ b/test/functional/test_helpers_test.rb
@@ -0,0 +1,38 @@
+#
+# There are a few test helpers for dealing with login etc.
+# We test them here and also document their behaviour.
+#
+
+require 'test_helper'
+
+class TestHelpersTest < ActionController::TestCase
+ tests ApplicationController # testing no controller in particular
+
+ def test_login_stubs_warden
+ login
+ assert_equal @current_user, request.env['warden'].user
+ end
+
+ def test_login_token_authenticates
+ login
+ assert_equal @current_user, @controller.send(:token_authenticate)
+ end
+
+ def test_login_stubs_token
+ login
+ assert @token
+ assert_equal @current_user, @token.authenticate
+ end
+
+ def test_login_adds_token_header
+ login
+ token_present = @controller.authenticate_with_http_token do |token, options|
+ assert_equal @token.id, token
+ end
+ # authenticate_with_http_token just returns nil and does not
+ # execute the block if there is no token. So we have to also
+ # ensure it was run:
+ assert token_present
+ end
+end
+
diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb
new file mode 100644
index 0000000..0713836
--- /dev/null
+++ b/test/functional/users_controller_test.rb
@@ -0,0 +1,165 @@
+require 'test_helper'
+
+class UsersControllerTest < ActionController::TestCase
+
+ test "should get new" do
+ get :new
+ assert_equal User, assigns(:user).class
+ assert_response :success
+ end
+
+ test "new should redirect logged in users" do
+ login
+ get :new
+ assert_response :redirect
+ assert_redirected_to home_path
+ end
+
+ test "failed show without login" do
+ user = find_record :user
+ get :show, :id => user.id
+ assert_response :redirect
+ assert_redirected_to login_path
+ end
+
+ test "user can see user" do
+ user = find_record :user,
+ :most_recent_tickets => []
+ login user
+ get :show, :id => user.id
+ assert_response :success
+ end
+
+ test "admin can see other user" do
+ user = find_record :user,
+ :most_recent_tickets => []
+ login :is_admin? => true
+ get :show, :id => user.id
+ assert_response :success
+
+ end
+
+ test "user cannot see other user" do
+ user = find_record :user,
+ :most_recent_tickets => []
+ login
+ get :show, :id => user.id
+ assert_response :redirect
+ assert_access_denied
+ end
+
+ test "may not show non-existing user without auth" do
+ nonid = 'thisisnotanexistinguserid'
+
+ get :show, :id => nonid
+ assert_access_denied(true, false)
+ end
+
+ test "may not show non-existing user without admin" do
+ nonid = 'thisisnotanexistinguserid'
+ login
+
+ get :show, :id => nonid
+ assert_access_denied
+ end
+
+ test "redirect admin to user list for non-existing user" do
+ nonid = 'thisisnotanexistinguserid'
+ login :is_admin? => true
+ get :show, :id => nonid
+ assert_response :redirect
+ assert_equal({:alert => "No such user."}, flash.to_hash)
+ assert_redirected_to users_path
+ end
+
+ test "should get edit view" do
+ user = find_record :user
+
+ login user
+ get :edit, :id => user.id
+
+ assert_equal user, assigns[:user]
+ end
+
+ test "admin can destroy user" do
+ user = find_record :user
+
+ # we destroy the user record and the associated data...
+ user.expects(:destroy)
+ Identity.expects(:disable_all_for).with(user)
+ Ticket.expects(:destroy_all_from).with(user)
+
+ login :is_admin? => true
+ delete :destroy, :id => user.id
+
+ assert_response :redirect
+ assert_redirected_to users_path
+ end
+
+ test "user can cancel account" do
+ user = find_record :user
+
+ # we destroy the user record and the associated data...
+ user.expects(:destroy)
+ Identity.expects(:disable_all_for).with(user)
+ Ticket.expects(:destroy_all_from).with(user)
+
+ login user
+ expect_logout
+ delete :destroy, :id => @current_user.id
+
+ assert_response :redirect
+ assert_redirected_to bye_url
+ end
+
+ test "non-admin can't destroy user" do
+ user = find_record :user
+
+ login
+ delete :destroy, :id => user.id
+
+ assert_access_denied
+ end
+
+ test "admin can list users" do
+ login :is_admin? => true
+ get :index
+
+ assert_response :success
+ assert assigns(:users)
+ end
+
+ test "non-admin can't list users" do
+ login
+ get :index
+
+ assert_access_denied
+ end
+
+ test "admin can search users" do
+ login :is_admin? => true
+ get :index, :query => "a"
+
+ assert_response :success
+ assert assigns(:users)
+ end
+
+ test "user cannot enable own account" do
+ user = find_record :user
+ login
+ post :enable, :id => user.id
+ assert_access_denied
+ end
+
+ test "admin can deactivate user" do
+ user = find_record :user
+ assert user.enabled?
+ user.expects(:save).returns(true)
+
+ login :is_admin? => true
+
+ post :deactivate, :id => user.id
+ assert !assigns(:user).enabled?
+ end
+
+end
diff --git a/test/functional/v1/certs_controller_test.rb b/test/functional/v1/certs_controller_test.rb
new file mode 100644
index 0000000..fb8e9c4
--- /dev/null
+++ b/test/functional/v1/certs_controller_test.rb
@@ -0,0 +1,46 @@
+require 'test_helper'
+
+class V1::CertsControllerTest < ActionController::TestCase
+
+ test "send unlimited cert without login" do
+ with_config allow_anonymous_certs: true do
+ cert = expect_cert('UNLIMITED')
+ get :show
+ assert_response :success
+ assert_equal cert.to_s, @response.body
+ end
+ end
+
+ test "send limited cert" do
+ with_config allow_limited_certs: true do
+ login
+ cert = expect_cert('LIMITED')
+ get :show
+ assert_response :success
+ assert_equal cert.to_s, @response.body
+ end
+ end
+
+ test "send unlimited cert" do
+ login effective_service_level: ServiceLevel.new(id: 2)
+ cert = expect_cert('UNLIMITED')
+ get :show
+ assert_response :success
+ assert_equal cert.to_s, @response.body
+ end
+
+ test "redirect if no eip service offered" do
+ get :show
+ assert_response :redirect
+ end
+
+ protected
+
+ def expect_cert(prefix)
+ cert = stub :to_s => "#{prefix.downcase} cert"
+ ClientCertificate.expects(:new).
+ with(:prefix => prefix).
+ returns(cert)
+ return cert
+ end
+end
diff --git a/test/functional/v1/messages_controller_test.rb b/test/functional/v1/messages_controller_test.rb
new file mode 100644
index 0000000..24a5b1f
--- /dev/null
+++ b/test/functional/v1/messages_controller_test.rb
@@ -0,0 +1,57 @@
+require 'test_helper'
+
+class V1::MessagesControllerTest < ActionController::TestCase
+
+ setup do
+ @user = FactoryGirl.build(:user)
+ @user.save
+ @message = Message.new(:text => 'a test message')
+ @message.user_ids_to_show << @user.id
+ @message.save
+ end
+
+ teardown do
+ @message.destroy
+ @user.destroy
+ end
+
+ test "get messages for user" do
+ login @user
+ get :index
+ assert response.body.include? @message.text
+ assert response.body.include? @message.id
+ end
+
+ test "mark message read for user" do
+ login @user
+ assert @message.user_ids_to_show.include?(@user.id)
+ assert !@message.user_ids_have_shown.include?(@user.id)
+ put :update, :id => @message.id
+ @message.reload
+ assert !@message.user_ids_to_show.include?(@user.id)
+ assert @message.user_ids_have_shown.include?(@user.id)
+ assert_json_response true
+ end
+
+ test "do not get seen messages" do
+ login @user
+ put :update, :id => @message.id
+ @message.reload
+ get :index
+ assert !(response.body.include? @message.text)
+ assert !(response.body.include? @message.id)
+ end
+
+
+ test "mark read responds even with bad inputs" do
+ login @user
+ put :update, :id => 'more nonsense'
+ assert_json_response false
+ end
+
+ test "fails if not authenticated" do
+ get :index, :format => :json
+ assert_access_denied
+ end
+
+end
diff --git a/test/functional/v1/services_controller_test.rb b/test/functional/v1/services_controller_test.rb
new file mode 100644
index 0000000..cde7d9f
--- /dev/null
+++ b/test/functional/v1/services_controller_test.rb
@@ -0,0 +1,29 @@
+require 'test_helper'
+
+class V1::ServicesControllerTest < ActionController::TestCase
+
+ test "anonymous user gets login required service info" do
+ get :show, format: :json
+ assert_json_response name: 'anonymous',
+ eip_rate_limit: false,
+ description: 'please login to access our services'
+ end
+
+ test "anonymous user gets vpn service info" do
+ with_config allow_anonymous_certs: true do
+ get :show, format: :json
+ assert_json_response name: 'anonymous',
+ eip_rate_limit: false,
+ description: 'anonymous access to the VPN'
+ end
+ end
+
+ test "user can see their service info" do
+ login
+ get :show, format: :json
+ default_level = APP_CONFIG[:default_service_level]
+ assert_json_response APP_CONFIG[:service_levels][default_level]
+ end
+
+end
+
diff --git a/test/functional/v1/sessions_controller_test.rb b/test/functional/v1/sessions_controller_test.rb
new file mode 100644
index 0000000..df0d681
--- /dev/null
+++ b/test/functional/v1/sessions_controller_test.rb
@@ -0,0 +1,62 @@
+require 'test_helper'
+
+# This is a simple controller unit test.
+# We're stubbing out both warden and srp.
+# There's an integration test testing the full rack stack and srp
+class V1::SessionsControllerTest < ActionController::TestCase
+
+ setup do
+ @request.env['HTTP_HOST'] = 'api.lvh.me'
+ @user = stub_record :user, {}, true
+ @client_hex = 'a123'
+ end
+
+ test "renders json" do
+ get :new, :format => :json
+ assert_response :success
+ assert_json_error nil
+ end
+
+ test "renders warden errors" do
+ request.env['warden.options'] = {attempted_path: 'path/to/controller'}
+ strategy = stub :message => {:field => :translate_me}
+ request.env['warden'].stubs(:winning_strategy).returns(strategy)
+ I18n.expects(:t).with(:translate_me).at_least_once.returns("translation stub")
+ get :new, :format => :json
+ assert_response 422
+ assert_json_error :field => "translation stub"
+ end
+
+ # Warden takes care of parsing the params and
+ # rendering the response. So not much to test here.
+ test "should perform handshake" do
+ request.env['warden'].expects(:authenticate!)
+ # make sure we don't get a template missing error:
+ @controller.stubs(:render)
+ post :create, :login => @user.login, 'A' => @client_hex
+ end
+
+ test "should authenticate" do
+ request.env['warden'].expects(:authenticate!)
+ @controller.stubs(:current_user).returns(@user)
+ handshake = stub(:to_hash => {h: "ash"})
+ session[:handshake] = handshake
+
+ post :update, :id => @user.login, :client_auth => @client_hex
+
+ assert_nil session[:handshake]
+ assert_response :success
+ assert json_response.keys.include?("id")
+ assert json_response.keys.include?("token")
+ assert token = Token.find(json_response['token'])
+ assert_equal @user.id, token.user_id
+ end
+
+ test "destroy should logout" do
+ login
+ expect_logout
+ delete :destroy
+ assert_response 204
+ end
+
+end
diff --git a/test/functional/v1/users_controller_test.rb b/test/functional/v1/users_controller_test.rb
new file mode 100644
index 0000000..7cd9b0c
--- /dev/null
+++ b/test/functional/v1/users_controller_test.rb
@@ -0,0 +1,74 @@
+require 'test_helper'
+
+class V1::UsersControllerTest < ActionController::TestCase
+
+ test "user can change settings" do
+ user = find_record :user
+ changed_attribs = record_attributes_for :user_with_settings
+ account_settings = stub
+ account_settings.expects(:update).with(changed_attribs)
+ Account.expects(:new).with(user).returns(account_settings)
+
+ login user
+ put :update, :user => changed_attribs, :id => user.id, :format => :json
+
+ assert_equal user, assigns[:user]
+ assert_response 204
+ assert_equal " ", @response.body
+ end
+
+ test "admin can update user" do
+ user = find_record :user
+ changed_attribs = record_attributes_for :user_with_settings
+ account_settings = stub
+ account_settings.expects(:update).with(changed_attribs)
+ Account.expects(:new).with(user).returns(account_settings)
+
+ login :is_admin? => true
+ put :update, :user => changed_attribs, :id => user.id, :format => :json
+
+ assert_equal user, assigns[:user]
+ assert_response 204
+ end
+
+ test "user cannot update other user" do
+ user = find_record :user
+ login
+ put :update, :user => record_attributes_for(:user_with_settings), :id => user.id, :format => :json
+ assert_access_denied
+ end
+
+ test "should create new user" do
+ user_attribs = record_attributes_for :user
+ user = User.new(user_attribs)
+ Account.expects(:create).with(user_attribs).returns(user)
+
+ post :create, :user => user_attribs, :format => :json
+
+ assert_nil session[:user_id]
+ assert_json_response user
+ assert_response :success
+ end
+
+ test "should redirect to signup form on failed attempt" do
+ user_attribs = record_attributes_for :user
+ user_attribs.slice!('login')
+ user = User.new(user_attribs)
+ assert !user.valid?
+ Account.expects(:create).with(user_attribs).returns(user)
+
+ post :create, :user => user_attribs, :format => :json
+
+ assert_json_error user.errors.messages
+ assert_response 422
+ end
+
+ test "admin can autocomplete users" do
+ login :is_admin? => true
+ get :index, :query => 'a', :format => :json
+
+ assert_response :success
+ assert assigns(:users)
+ end
+
+end
diff --git a/test/functional/webfinger_controller_test.rb b/test/functional/webfinger_controller_test.rb
new file mode 100644
index 0000000..6597b69
--- /dev/null
+++ b/test/functional/webfinger_controller_test.rb
@@ -0,0 +1,33 @@
+require 'test_helper'
+
+class WebfingerControllerTest < ActionController::TestCase
+
+ test "get host meta xml" do
+ get :host_meta, :format => :xml
+ assert_response :success
+ assert_equal "application/xml", response.content_type
+ end
+
+ test "get host meta json" do
+ get :host_meta, :format => :json
+ assert_response :success
+ assert_equal "application/json", response.content_type
+ end
+
+ test "get user webfinger xml" do
+ @user = stub_record :user, :public_key => 'my public key'
+ User.stubs(:find_by_login).with(@user.login).returns(@user)
+ get :search, :q => @user.email_address.to_s, :format => :xml
+ assert_response :success
+ assert_equal "application/xml", response.content_type
+ end
+
+ test "get user webfinger json" do
+ @user = stub_record :user, :public_key => 'my public key'
+ User.stubs(:find_by_login).with(@user.login).returns(@user)
+ get :search, :q => @user.email_address.to_s, :format => :json
+ assert_response :success
+ assert_equal "application/json", response.content_type
+ end
+
+end
diff --git a/test/integration/api/Readme.md b/test/integration/api/Readme.md
new file mode 100644
index 0000000..04363bd
--- /dev/null
+++ b/test/integration/api/Readme.md
@@ -0,0 +1,23 @@
+API tests
+==========
+
+
+Testing the restful api from a simple python client as that's what we'll be using.
+
+This test so far mostly demoes the API. We have no SRP calc in there.
+
+TODO: keep track of the cookies during login. The server uses the session to keep track of the random numbers A and B.
+
+The output of signup_and_login_wrong_password pretty well describes the SRP API:
+
+```
+POST: http://localhost:9292/users.json
+ {"user[password_salt]": "54321", "user[password_verifier]": "12345", "user[login]": "SWQ055"}
+ -> {"password_salt":"54321","login":"SWQ055"}
+POST: http://localhost:9292/sessions
+ {"A": "12345", "login": "SWQ055"}
+ -> {"B":"1778367531e93a4c7713c76f67649f35a4211ebc520926ae8c3848cd66171651"}
+PUT: http://localhost:9292/sessions/SWQ055
+ {"M": "123ABC"}
+ -> {"errors":[{"login":"Not a valid username/password combination"},{"password":"Not a valid username/password combination"}]}
+```
diff --git a/test/integration/api/login_test.rb b/test/integration/api/login_test.rb
new file mode 100644
index 0000000..92d153f
--- /dev/null
+++ b/test/integration/api/login_test.rb
@@ -0,0 +1,50 @@
+require 'test_helper'
+require_relative 'srp_test'
+
+class LoginTest < SrpTest
+
+ setup do
+ register_user
+ end
+
+ test "requires handshake before validation" do
+ validate("bla")
+ assert_json_error login: I18n.t(:all_strategies_failed)
+ end
+
+ test "login with srp" do
+ authenticate
+ assert_equal ["M2", "id", "token"], server_auth.keys
+ assert last_response.successful?
+ assert_nil server_auth["errors"]
+ assert server_auth["M2"]
+ end
+
+ test "wrong password login attempt" do
+ authenticate password: "wrong password"
+ assert_json_error "base" => "Not a valid username/password combination"
+ assert !last_response.successful?
+ assert_nil server_auth["M2"]
+ end
+
+ test "wrong username login attempt" do
+ assert_raises RECORD_NOT_FOUND do
+ authenticate login: "wrong login"
+ end
+ assert_json_error "base" => "Not a valid username/password combination"
+ assert !last_response.successful?
+ assert_nil server_auth
+ end
+
+ test "logout" do
+ authenticate
+ logout
+ assert_equal 204, last_response.status
+ end
+
+ test "logout requires token" do
+ authenticate
+ logout(nil, {})
+ assert_equal 422, last_response.status
+ end
+end
diff --git a/test/integration/api/pgp_key_test.rb b/test/integration/api/pgp_key_test.rb
new file mode 100644
index 0000000..4c7fb4c
--- /dev/null
+++ b/test/integration/api/pgp_key_test.rb
@@ -0,0 +1,35 @@
+require 'test_helper'
+require_relative 'srp_test'
+
+class PgpKeyTest < SrpTest
+
+ setup do
+ # todo: prepare user and login without doing the srp dance
+ register_user
+ authenticate
+ end
+
+ test "upload pgp key" do
+ update_user public_key: key
+ assert_equal key, Identity.for(@user).keys[:pgp]
+ end
+
+ # eventually probably want to remove most of this into a non-integration
+ # functional test
+ test "prevent uploading invalid key" do
+ update_user public_key: "invalid key"
+ assert_nil Identity.for(@user).keys[:pgp]
+ end
+
+ test "prevent emptying public key" do
+ update_user public_key: key
+ update_user public_key: ""
+ assert_equal key, Identity.for(@user).keys[:pgp]
+ end
+
+ protected
+
+ def key
+ @key ||= FactoryGirl.build :pgp_key
+ end
+end
diff --git a/test/integration/api/python/flow_with_srp.py b/test/integration/api/python/flow_with_srp.py
new file mode 100755
index 0000000..9fc168b
--- /dev/null
+++ b/test/integration/api/python/flow_with_srp.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python
+
+# under development
+
+import requests
+import json
+import string
+import random
+import srp._pysrp as srp
+import binascii
+
+safe_unhexlify = lambda x: binascii.unhexlify(x) if (len(x) % 2 == 0) else binascii.unhexlify('0'+x)
+
+# using globals for now
+# server = 'https://dev.bitmask.net/1'
+server = 'http://api.lvh.me:3000/1'
+
+def run_tests():
+ login = 'test_' + id_generator()
+ password = id_generator() + id_generator()
+ usr = srp.User( login, password, srp.SHA256, srp.NG_1024 )
+ print_and_parse(signup(login, password))
+
+ auth = print_and_parse(authenticate(usr))
+ verify_or_debug(auth, usr)
+ assert usr.authenticated()
+
+ usr = change_password(auth['id'], login, auth['token'])
+
+ auth = print_and_parse(authenticate(usr))
+ verify_or_debug(auth, usr)
+ # At this point the authentication process is complete.
+ assert usr.authenticated()
+
+# let's have some random name
+def id_generator(size=6, chars=string.ascii_lowercase + string.digits):
+ return ''.join(random.choice(chars) for x in range(size))
+
+# log the server communication
+def print_and_parse(response):
+ request = response.request
+ print request.method + ': ' + response.url
+ if hasattr(request, 'data'):
+ print " " + json.dumps(response.request.data)
+ print " -> " + response.text
+ try:
+ return json.loads(response.text)
+ except ValueError:
+ return None
+
+def signup(login, password):
+ salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 )
+ user_params = {
+ 'user[login]': login,
+ 'user[password_verifier]': binascii.hexlify(vkey),
+ 'user[password_salt]': binascii.hexlify(salt)
+ }
+ return requests.post(server + '/users.json', data = user_params, verify = False)
+
+def change_password(user_id, login, token):
+ password = id_generator() + id_generator()
+ salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 )
+ user_params = {
+ 'user[password_verifier]': binascii.hexlify(vkey),
+ 'user[password_salt]': binascii.hexlify(salt)
+ }
+ auth_headers = { 'Authorization': 'Token token="' + token + '"'}
+ print user_params
+ print_and_parse(requests.put(server + '/users/' + user_id + '.json', data = user_params, verify = False, headers = auth_headers))
+ return srp.User( login, password, srp.SHA256, srp.NG_1024 )
+
+
+def authenticate(usr):
+ session = requests.session()
+ uname, A = usr.start_authentication()
+ params = {
+ 'login': uname,
+ 'A': binascii.hexlify(A)
+ }
+ init = print_and_parse(session.post(server + '/sessions', data = params, verify=False))
+ M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B']) )
+ return session.put(server + '/sessions/' + uname, verify = False,
+ data = {'client_auth': binascii.hexlify(M)})
+
+def verify_or_debug(auth, usr):
+ if ( 'errors' in auth ):
+ print ' u = "%x"' % usr.u
+ print ' x = "%x"' % usr.x
+ print ' v = "%x"' % usr.v
+ print ' S = "%x"' % usr.S
+ print ' K = "' + binascii.hexlify(usr.K) + '"'
+ print ' M = "' + binascii.hexlify(usr.M) + '"'
+ else:
+ usr.verify_session( safe_unhexlify(auth["M2"]) )
+
+run_tests()
diff --git a/test/integration/api/python/login_wrong_username.py b/test/integration/api/python/login_wrong_username.py
new file mode 100755
index 0000000..390f250
--- /dev/null
+++ b/test/integration/api/python/login_wrong_username.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+server = 'http://localhost:3000'
+
+import requests
+import json
+import string
+import random
+
+def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
+ return ''.join(random.choice(chars) for x in range(size))
+
+params = {
+ 'login': 'python_test_user_'+id_generator(),
+ 'A': '12345',
+ }
+r = requests.post(server + '/sessions', data = params)
+print r.url
+print r.text
diff --git a/test/integration/api/python/signup.py b/test/integration/api/python/signup.py
new file mode 100755
index 0000000..0d3a4e0
--- /dev/null
+++ b/test/integration/api/python/signup.py
@@ -0,0 +1,20 @@
+#!/usr/bin/env python
+
+server = 'http://localhost:3000'
+
+import requests
+import json
+import string
+import random
+
+def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
+ return ''.join(random.choice(chars) for x in range(size))
+
+user_params = {
+ 'user[login]': 'python_test_user_'+id_generator(),
+ 'user[password_verifier]': '12345',
+ 'user[password_salt]': '54321'
+ }
+r = requests.post(server + '/users.json', data = user_params)
+print r.url
+print r.text
diff --git a/test/integration/api/python/signup_and_login.py b/test/integration/api/python/signup_and_login.py
new file mode 100755
index 0000000..ac611d7
--- /dev/null
+++ b/test/integration/api/python/signup_and_login.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+# FAILS
+#
+# This test is currently failing for me because the session is not kept.
+# Played with it a bunch - is probably messed up right now as well.
+
+
+server = 'http://localhost:3000'
+
+import requests
+import json
+import string
+import random
+
+def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
+ return ''.join(random.choice(chars) for x in range(size))
+
+def print_and_parse(response):
+ print response.request.method + ': ' + response.url
+ print " " + json.dumps(response.request.data)
+ print " -> " + response.text
+ return json.loads(response.text)
+
+def signup(session):
+ user_params = {
+ 'user[login]': id_generator(),
+ 'user[password_verifier]': '12345',
+ 'user[password_salt]': 'AB54321'
+ }
+ return session.post(server + '/users.json', data = user_params)
+
+def authenticate(session, login):
+ params = {
+ 'login': login,
+ 'A': '12345',
+ }
+ init = print_and_parse(session.post(server + '/sessions', data = params))
+ return session.put(server + '/sessions/' + login, data = {'client_auth': '123'})
+
+session = requests.session()
+user = print_and_parse(signup(session))
+# SRP signup would happen here and calculate M hex
+auth = print_and_parse(authenticate(session, user['login']))
diff --git a/test/integration/api/python/signup_and_login_wrong_password.py b/test/integration/api/python/signup_and_login_wrong_password.py
new file mode 100755
index 0000000..9efffa1
--- /dev/null
+++ b/test/integration/api/python/signup_and_login_wrong_password.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+server = 'http://localhost:9292'
+
+import requests
+import json
+import string
+import random
+
+def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
+ return ''.join(random.choice(chars) for x in range(size))
+
+def print_and_parse(response):
+ print response.request.method + ': ' + response.url
+ print " " + json.dumps(response.request.data)
+ print " -> " + response.text
+# print " () " + json.dumps(requests.utils.dict_from_cookiejar(response.cookies))
+ return json.loads(response.text)
+
+def signup():
+ user_params = {
+ 'user[login]': id_generator(),
+ 'user[password_verifier]': '12345',
+ 'user[password_salt]': '54321'
+ }
+ return requests.post(server + '/users.json', data = user_params)
+
+def handshake(login):
+ params = {
+ 'login': login,
+ 'A': '12345',
+ }
+ return requests.post(server + '/sessions', data = params)
+
+def authenticate(login, M):
+ return requests.put(server + '/sessions/' + login, data = {'M': M})
+
+
+user = print_and_parse(signup())
+handshake = print_and_parse(handshake(user['login']))
+# SRP signup would happen here and calculate M hex
+M = '123ABC'
+auth = print_and_parse(authenticate(user['login'], M))
diff --git a/test/integration/api/python/umlauts.py b/test/integration/api/python/umlauts.py
new file mode 100755
index 0000000..96fecbf
--- /dev/null
+++ b/test/integration/api/python/umlauts.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+# coding: utf-8
+
+# under development
+
+import requests
+import json
+import string
+import random
+import srp._pysrp as srp
+import binascii
+
+safe_unhexlify = lambda x: binascii.unhexlify(x) if (len(x) % 2 == 0) else binascii.unhexlify('0'+x)
+
+# using globals for now
+# server = 'https://dev.bitmask.net/1'
+server = 'http://api.lvh.me:3000/1'
+
+def run_tests():
+ login = 'test_' + id_generator()
+ password = id_generator() + "äöì" + id_generator()
+ usr = srp.User( login, password, srp.SHA256, srp.NG_1024 )
+ print_and_parse(signup(login, password))
+
+ auth = print_and_parse(authenticate(usr))
+ verify_or_debug(auth, usr)
+ assert usr.authenticated()
+
+
+# let's have some random name
+def id_generator(size=6, chars=string.ascii_lowercase + string.digits):
+ return ''.join(random.choice(chars) for x in range(size))
+
+# log the server communication
+def print_and_parse(response):
+ request = response.request
+ print request.method + ': ' + response.url
+ if hasattr(request, 'data'):
+ print " " + json.dumps(response.request.data)
+ print " -> " + response.text
+ try:
+ return json.loads(response.text)
+ except ValueError:
+ return None
+
+def signup(login, password):
+ salt, vkey = srp.create_salted_verification_key( login, password, srp.SHA256, srp.NG_1024 )
+ user_params = {
+ 'user[login]': login,
+ 'user[password_verifier]': binascii.hexlify(vkey),
+ 'user[password_salt]': binascii.hexlify(salt)
+ }
+ print json.dumps(user_params)
+ return requests.post(server + '/users.json', data = user_params, verify = False)
+
+def authenticate(usr):
+ session = requests.session()
+ uname, A = usr.start_authentication()
+ params = {
+ 'login': uname,
+ 'A': binascii.hexlify(A)
+ }
+ init = print_and_parse(session.post(server + '/sessions', data = params, verify=False))
+ M = usr.process_challenge( safe_unhexlify(init['salt']), safe_unhexlify(init['B']) )
+ return session.put(server + '/sessions/' + uname, verify = False,
+ data = {'client_auth': binascii.hexlify(M)})
+
+def verify_or_debug(auth, usr):
+ if ( 'errors' in auth ):
+ print ' u = "%x"' % usr.u
+ print ' x = "%x"' % usr.x
+ print ' v = "%x"' % usr.v
+ print ' S = "%x"' % usr.S
+ print ' K = "' + binascii.hexlify(usr.K) + '"'
+ print ' M = "' + binascii.hexlify(usr.M) + '"'
+ else:
+ usr.verify_session( safe_unhexlify(auth["M2"]) )
+
+run_tests()
diff --git a/test/integration/api/signup_test.rb b/test/integration/api/signup_test.rb
new file mode 100644
index 0000000..236c547
--- /dev/null
+++ b/test/integration/api/signup_test.rb
@@ -0,0 +1,20 @@
+require 'test_helper'
+require_relative 'srp_test'
+
+class SignupTest < SrpTest
+
+ setup do
+ register_user
+ end
+
+ test "signup response" do
+ assert_json_response :login => @login, :ok => true
+ assert last_response.successful?
+ end
+
+ test "signup creates user" do
+ assert @user
+ assert_equal @login, @user.login
+ end
+end
+
diff --git a/test/integration/api/srp_test.rb b/test/integration/api/srp_test.rb
new file mode 100644
index 0000000..26adc8c
--- /dev/null
+++ b/test/integration/api/srp_test.rb
@@ -0,0 +1,105 @@
+class SrpTest < RackTest
+ include AssertResponses
+
+ teardown do
+ if @user
+ cleanup_user
+ end
+ Warden.test_reset!
+ end
+
+ # this test wraps the api and implements the interface the ruby-srp client.
+ def handshake(login, aa)
+ post "http://api.lvh.me:3000/1/sessions.json",
+ :login => login,
+ 'A' => aa,
+ :format => :json
+ response = JSON.parse(last_response.body)
+ if response['errors']
+ raise RECORD_NOT_FOUND.new(response['errors'])
+ else
+ return response['B']
+ end
+ end
+
+ def validate(m)
+ put "http://api.lvh.me:3000/1/sessions/" + @login + '.json',
+ :client_auth => m,
+ :format => :json
+ return JSON.parse(last_response.body)
+ end
+
+ protected
+
+ attr_reader :server_auth
+
+ def register_user(login = "integration_test_user", password = 'srp, verify me!')
+ cleanup_user(login)
+ post 'http://api.lvh.me:3000/1/users.json',
+ user_params(login: login, password: password)
+ @user = User.find_by_login(login)
+ @login = login
+ @password = password
+ end
+
+ def update_user(params)
+ put "http://api.lvh.me:3000/1/users/" + @user.id + '.json',
+ user_params(params),
+ auth_headers
+ end
+
+ def authenticate(params = nil)
+ @server_auth = srp(params).authenticate(self)
+ end
+
+ def auth_headers
+ return {} if @server_auth.nil?
+ {
+ "HTTP_AUTHORIZATION" => encoded_token
+ }
+ end
+
+ def encoded_token
+ ActionController::HttpAuthentication::Token.encode_credentials(server_auth["token"])
+ end
+
+ def logout(params=nil, headers=nil)
+ delete "http://api.lvh.me:3000/1/logout.json",
+ params || {format: :json},
+ headers || auth_headers
+ end
+
+ def cleanup_user(login = nil)
+ login ||= @user.login
+ Identity.by_address.key(login + '@' + APP_CONFIG[:domain]).each do |identity|
+ identity.destroy
+ end
+ if user = User.find_by_login(login)
+ user.destroy
+ end
+ end
+
+ def user_params(params)
+ if params.keys.include?(:password)
+ srp_process_password(params)
+ end
+ return { user: params, format: :json }
+ end
+
+ def srp_process_password(params)
+ params.reverse_merge! login: @login, salt: @salt
+ @srp = SRP::Client.new params[:login], password: params.delete(:password)
+ @salt = srp.salt.to_s(16)
+ params.merge! :password_verifier => srp.verifier.to_s(16),
+ :password_salt => @salt
+ end
+
+ def srp(params = nil)
+ if params.nil?
+ @srp
+ else
+ params.reverse_merge! password: @password
+ SRP::Client.new(params.delete(:login) || @login, params)
+ end
+ end
+end
diff --git a/test/integration/api/update_account_test.rb b/test/integration/api/update_account_test.rb
new file mode 100644
index 0000000..63429e7
--- /dev/null
+++ b/test/integration/api/update_account_test.rb
@@ -0,0 +1,51 @@
+require 'test_helper'
+require_relative 'srp_test'
+
+class UpdateAccountTest < SrpTest
+
+ setup do
+ register_user
+ end
+
+ test "require authentication" do
+ update_user password: "No! Verify me instead."
+ assert_access_denied
+ end
+
+ test "require token" do
+ authenticate
+ put "http://api.lvh.me:3000/1/users/" + @user.id + '.json',
+ user_params(password: "No! Verify me instead.")
+ assert_access_denied
+ end
+
+ test "update password via api" do
+ authenticate
+ update_user password: "No! Verify me instead."
+ authenticate
+ assert last_response.successful?
+ assert_nil server_auth["errors"]
+ assert server_auth["M2"]
+ end
+
+ test "change login with password_verifier" do
+ authenticate
+ new_login = 'zaph'
+ cleanup_user new_login
+ update_user login: new_login, password: @password
+ authenticate
+ assert last_response.successful?
+ assert_equal new_login, @user.reload.login
+ end
+
+ test "prevent changing login without changing password_verifier" do
+ authenticate
+ original_login = @user.login
+ new_login = 'zaph'
+ cleanup_user new_login
+ update_user login: new_login
+ assert last_response.successful?
+ # does not change login if no password_verifier is present
+ assert_equal original_login, @user.reload.login
+ end
+end
diff --git a/test/integration/browser/account_test.rb b/test/integration/browser/account_test.rb
new file mode 100644
index 0000000..491a9e1
--- /dev/null
+++ b/test/integration/browser/account_test.rb
@@ -0,0 +1,164 @@
+require 'test_helper'
+
+class AccountTest < BrowserIntegrationTest
+
+ teardown do
+ Identity.destroy_all_disabled
+ end
+
+ test "signup successfully" do
+ username, password = submit_signup
+ assert page.has_content?("Welcome #{username}")
+ click_on 'Logout'
+ assert page.has_content?("Log In")
+ assert_equal '/', current_path
+ assert user = User.find_by_login(username)
+ user.account.destroy
+ end
+
+ test "signup with username ending in dot json" do
+ username = Faker::Internet.user_name + '.json'
+ submit_signup username
+ assert page.has_content?("Welcome #{username}")
+ end
+
+ test "successful login" do
+ username, password = submit_signup
+ click_on 'Logout'
+ attempt_login(username, password)
+ assert page.has_content?("Welcome #{username}")
+ within('.sidenav li.active') do
+ assert page.has_content?("Overview")
+ end
+ User.find_by_login(username).account.destroy
+ end
+
+ test "failed login" do
+ visit '/'
+ attempt_login("username", "wrong password")
+ assert_invalid_login(page)
+ end
+
+ test "account destruction" do
+ username, password = submit_signup
+ click_on I18n.t('account_settings')
+ click_on I18n.t('destroy_my_account')
+ assert page.has_content?(I18n.t('account_destroyed'))
+ attempt_login(username, password)
+ assert_invalid_login(page)
+ end
+
+ test "handle blocked after account destruction" do
+ username, password = submit_signup
+ click_on I18n.t('account_settings')
+ click_on I18n.t('destroy_my_account')
+ submit_signup(username)
+ assert page.has_content?('has already been taken')
+ end
+
+ test "default user actions" do
+ login
+ click_on "Account Settings"
+ assert page.has_content? I18n.t('destroy_my_account')
+ assert page.has_no_css? '#update_login_and_password'
+ assert page.has_no_css? '#update_pgp_key'
+ end
+
+ test "default admin actions" do
+ login
+ with_config admins: [@user.login] do
+ click_on "Account Settings"
+ assert page.has_content? I18n.t('destroy_my_account')
+ assert page.has_no_css? '#update_login_and_password'
+ assert page.has_css? '#update_pgp_key'
+ end
+ end
+
+ test "change password" do
+ with_config user_actions: ['change_password'] do
+ login
+ click_on "Account Settings"
+ within('#update_login_and_password') do
+ fill_in 'Password', with: "other password"
+ fill_in 'Password confirmation', with: "other password"
+ click_on 'Save'
+ end
+ click_on 'Logout'
+ attempt_login(@user.login, "other password")
+ assert page.has_content?("Welcome #{@user.login}")
+ end
+ end
+
+ test "change pgp key" do
+ with_config user_actions: ['change_pgp_key'] do
+ pgp_key = FactoryGirl.build :pgp_key
+ login
+ click_on "Account Settings"
+ within('#update_pgp_key') do
+ fill_in 'Public key', with: pgp_key
+ click_on 'Save'
+ end
+ page.assert_selector 'input[value="Saving..."]'
+ # at some point we're done:
+ page.assert_no_selector 'input[value="Saving..."]'
+ assert page.has_field? 'Public key', with: pgp_key.to_s
+ assert_equal pgp_key, @user.reload.public_key
+ end
+ end
+
+
+ # trying to seed an invalid A for srp login
+ test "detects attempt to circumvent SRP" do
+ user = FactoryGirl.create :user
+ visit '/login'
+ fill_in 'Username', with: user.login
+ fill_in 'Password', with: "password"
+ inject_malicious_js
+ click_on 'Log In'
+ assert page.has_content?("Invalid random key")
+ assert page.has_no_content?("Welcome")
+ user.destroy
+ end
+
+ test "reports internal server errors" do
+ V1::UsersController.any_instance.stubs(:create).raises
+ submit_signup
+ assert page.has_content?("server failed")
+ end
+
+ test "does not render signup form without js" do
+ Capybara.current_driver = :rack_test # no js
+ visit '/signup'
+ assert page.has_no_content?("Username")
+ assert page.has_no_content?("Password")
+ end
+
+ test "does not render login form without js" do
+ Capybara.current_driver = :rack_test # no js
+ visit '/login'
+ assert page.has_no_content?("Username")
+ assert page.has_no_content?("Password")
+ end
+
+ def attempt_login(username, password)
+ click_on 'Log In'
+ fill_in 'Username', with: username
+ fill_in 'Password', with: password
+ click_on 'Log In'
+ end
+
+ def assert_invalid_login(page)
+ assert page.has_selector? '.btn-primary.disabled'
+ assert page.has_content? I18n.t(:invalid_user_pass)
+ assert page.has_no_selector? '.btn-primary.disabled'
+ end
+
+ def inject_malicious_js
+ page.execute_script <<-EOJS
+ var calc = new srp.Calculate();
+ calc.A = function(_a) {return "00";};
+ calc.S = calc.A;
+ srp.session = new srp.Session(null, calc);
+ EOJS
+ end
+end
diff --git a/test/integration/browser/password_validation_test.rb b/test/integration/browser/password_validation_test.rb
new file mode 100644
index 0000000..45eb0bf
--- /dev/null
+++ b/test/integration/browser/password_validation_test.rb
@@ -0,0 +1,31 @@
+require 'test_helper'
+
+class PasswordValidationTest < BrowserIntegrationTest
+
+ test "password confirmation is validated" do
+ username ||= "test_#{SecureRandom.urlsafe_base64}".downcase
+ password ||= SecureRandom.base64
+ visit '/users/new'
+ fill_in 'Username', with: username
+ fill_in 'Password', with: password
+ fill_in 'Password confirmation', with: password + "-typo"
+ click_on 'Sign Up'
+ assert page.has_content? "does not match."
+ assert_equal '/users/new', current_path
+ assert page.has_selector? ".error #srp_password_confirmation"
+ end
+
+ test "password needs to be at least 8 chars long" do
+ username ||= "test_#{SecureRandom.urlsafe_base64}".downcase
+ password ||= SecureRandom.base64[0,7]
+ visit '/users/new'
+ fill_in 'Username', with: username
+ fill_in 'Password', with: password
+ fill_in 'Password confirmation', with: password
+ click_on 'Sign Up'
+ assert page.has_content? "needs to be at least 8 characters long"
+ assert_equal '/users/new', current_path
+ assert page.has_selector? ".error #srp_password"
+ end
+end
+
diff --git a/test/integration/browser/session_test.rb b/test/integration/browser/session_test.rb
new file mode 100644
index 0000000..fb20847
--- /dev/null
+++ b/test/integration/browser/session_test.rb
@@ -0,0 +1,17 @@
+require 'test_helper'
+
+class SessionTest < BrowserIntegrationTest
+
+ test "valid session" do
+ login
+ assert page.has_content?("Logout")
+ end
+
+ test "expired session" do
+ login
+ pretend_now_is(Time.now + 80.minutes) do
+ visit '/'
+ assert page.has_content?("Log In")
+ end
+ end
+end
diff --git a/test/integration/navigation_test.rb b/test/integration/navigation_test.rb
new file mode 100644
index 0000000..eec8c0e
--- /dev/null
+++ b/test/integration/navigation_test.rb
@@ -0,0 +1,9 @@
+require 'test_helper'
+
+class NavigationTest < ActionDispatch::IntegrationTest
+
+ # test "the truth" do
+ # assert true
+ # end
+end
+
diff --git a/test/integration/os_detection_test.rb b/test/integration/os_detection_test.rb
deleted file mode 100644
index 6d9a648..0000000
--- a/test/integration/os_detection_test.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'test_helper'
-
-class OsDetectionTest < BrowserIntegrationTest
-
- test "old windows shows deactivated download" do
- page.driver.add_headers "User-Agent" => "Win98"
- visit '/'
- assert_selector "html.oldwin"
- assert has_text? "not available"
- end
-
- test "android shows android download" do
- page.driver.add_headers "User-Agent" => "Android"
- visit '/'
- assert_selector "html.android"
- assert has_no_text? "not available"
- assert_selector "small", text: "Android"
- end
-
-end
diff --git a/test/leap_web_users_test.rb b/test/leap_web_users_test.rb
new file mode 100644
index 0000000..f142e54
--- /dev/null
+++ b/test/leap_web_users_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class LeapWebUsersTest < ActiveSupport::TestCase
+ test "module exists" do
+ assert_kind_of Module, LeapWebUsers
+ end
+end
diff --git a/test/nagios/soledad_sync.py b/test/nagios/soledad_sync.py
index 3f176b5..617dd3a 100755
--- a/test/nagios/soledad_sync.py
+++ b/test/nagios/soledad_sync.py
@@ -7,13 +7,15 @@
import tempfile
-import requests
import os
-import srp._pysrp as srp
import shutil
import u1db
+from support.api import Api
+from support.config import Config
+from support.user import User
+
+
from u1db.remote.http_target import HTTPSyncTarget
-from webapp_login import read_config, parse, authenticate, fail
# monkey patch U1DB's HTTPSyncTarget to perform token based auth
@@ -37,58 +39,39 @@ HTTPSyncTarget._sign_request = _sign_request
# to actually use the Soledad client in the future.
def get_soledad_info(config, tempdir):
- # get login and get user info
- user = config['user']
- api = config['api']
- usr = srp.User( user['username'], user['password'], srp.SHA256, srp.NG_1024 )
- try:
- auth = parse(authenticate(api, usr))
- except requests.exceptions.ConnectionError:
- fail('no connection to server')
- # get soledad server url
- service_url = 'https://%s:%d/%d/config/soledad-service.json' % \
- (api['domain'], api['port'], api['version'])
- soledad_hosts = requests.get(service_url).json['hosts']
- host = soledad_hosts.keys()[0]
- server_url = 'https://%s:%d/user-%s' % \
- (soledad_hosts[host]['hostname'], soledad_hosts[host]['port'],
- auth['id'])
- # get provider ca certificate
- #ca_cert = requests.get('https://127.0.0.1/ca.crt', verify=False).text
- #cert_file = os.path.join(tempdir, 'ca.crt')
- cert_file = None # not used for now
- #with open(cert_file, 'w') as f:
- # f.write(ca_cert)
- return auth['id'], user['password'], server_url, cert_file, auth['token']
-
-
-def run_tests():
- tempdir = tempfile.mkdtemp()
- uuid, password, server_url, cert_file, token = \
- get_soledad_info(read_config(), tempdir)
- exc = None
- try:
- # in the future, we can replace the following by an actual Soledad
- # client sync, if needed
- db = u1db.open(os.path.join(tempdir, '%s.db' % uuid), True)
- creds = {'token': {'uuid': uuid, 'token': token}}
- db.sync(server_url, creds=creds, autocreate=False)
- except Exception as e:
- exc = e
- shutil.rmtree(tempdir)
- exit(report(exc))
-
-
-def report(exc):
- if exc is None:
- print '0 soledad_sync - OK - can sync soledad fine'
- return 0
- if isinstance(exc, u1db.errors.U1DBError):
- print '2 soledad_sync - CRITICAL - ' + exc.message
- else:
- print '2 soledad_sync - CRITICAL - ' + str(exc)
- return 2
-
+ # get login and get user info
+ user = User(config)
+ api = Api(config, verify=False)
+ auth = user.login(api)
+ # get soledad server url
+ soledad_hosts = api.get('config/soledad-service.json')['hosts']
+ host = soledad_hosts.keys()[0]
+ server_url = 'https://%s:%d/user-%s' % \
+ (soledad_hosts[host]['hostname'], soledad_hosts[host]['port'],
+ auth['id'])
+ # get provider ca certificate
+ #ca_cert = requests.get('https://127.0.0.1/ca.crt', verify=False).text
+ #cert_file = os.path.join(tempdir, 'ca.crt')
+ cert_file = None # not used for now
+ #with open(cert_file, 'w') as f:
+ # f.write(ca_cert)
+ return auth['id'], server_url, cert_file, auth['token']
+
+
+def can_sync_soledad_fine():
+ tempdir = tempfile.mkdtemp()
+ try:
+ uuid, server_url, cert_file, token = \
+ get_soledad_info(Config(), tempdir)
+ # in the future, we can replace the following by an actual Soledad
+ # client sync, if needed
+ db = u1db.open(os.path.join(tempdir, '%s.db' % uuid), True)
+ creds = {'token': {'uuid': uuid, 'token': token}}
+ db.sync(server_url, creds=creds, autocreate=False)
+ finally:
+ shutil.rmtree(tempdir)
if __name__ == '__main__':
- run_tests()
+ from support import nagios_test
+ exit_code = nagios_test.run(can_sync_soledad_fine)
+ exit(exit_code)
diff --git a/test/nagios/support/__init__.py b/test/nagios/support/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/nagios/support/__init__.py
diff --git a/test/nagios/support/api.py b/test/nagios/support/api.py
new file mode 100644
index 0000000..ec1af99
--- /dev/null
+++ b/test/nagios/support/api.py
@@ -0,0 +1,39 @@
+import requests
+import json
+
+class Api():
+ def __init__(self, config, verify=True):
+ self.config = config.api
+ self.session = requests.session()
+ self.verify = verify
+
+ def api_url(self, path):
+ return self.api_root() + path
+
+ def api_root(self):
+ return "https://{domain}:{port}/{version}/".format(**self.config)
+
+ def get(self, path, **args):
+ response = self.session.get(self.api_url(path),
+ verify=self.verify,
+ **args)
+ return self.parse_json(response)
+
+ def post(self, path, **args):
+ response = self.session.post(self.api_url(path),
+ verify=self.verify,
+ **args)
+ return self.parse_json(response)
+
+ def put(self, path, **args):
+ response = self.session.put(self.api_url(path),
+ verify=self.verify,
+ **args)
+ return self.parse_json(response)
+
+ def parse_json(self, response):
+ try:
+ return response.json()
+ except TypeError:
+ return response.json # older versions of requests
+
diff --git a/test/nagios/support/config.py b/test/nagios/support/config.py
new file mode 100644
index 0000000..afb4464
--- /dev/null
+++ b/test/nagios/support/config.py
@@ -0,0 +1,14 @@
+import yaml
+
+class Config():
+ def __init__(self, filename="/etc/leap/hiera.yaml"):
+ with open("/etc/leap/hiera.yaml", 'r') as stream:
+ config = yaml.load(stream)
+ self.user = config['webapp']['nagios_test_user']
+ if 'username' not in self.user:
+ raise Exception('nagios test user lacks username')
+ if 'password' not in self.user:
+ raise Exception('nagios test user lacks password')
+ self.api = config['api']
+ self.api['version'] = config['webapp']['api_version']
+
diff --git a/test/nagios/support/nagios_report.py b/test/nagios/support/nagios_report.py
new file mode 100644
index 0000000..13cd551
--- /dev/null
+++ b/test/nagios/support/nagios_report.py
@@ -0,0 +1,24 @@
+def functions_for_system(under_test):
+ """
+ returns a set of functions to use for nagios reporting:
+ >>> ok, warn, critical, unknown = functions_for_system("tested system")
+
+ each of them will print a nagios line with its argument and
+ return the exit code:
+ >>> warn("that looks strange")
+ 1 tested system - WARNING - that looks strange
+ 1
+ """
+ def report_function(code):
+ return lambda message : report(under_test, code, message)
+ return map(report_function, [0,1,2,3])
+
+def report(system, code, message):
+ codes = {0: 'OK', 1: 'WARNING', 2: 'CRITICAL', 3: 'UNKNOWN'}
+ print "%d %s - %s - %s" % \
+ (code, system, codes[code], message)
+ return code
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/test/nagios/support/nagios_test.py b/test/nagios/support/nagios_test.py
new file mode 100644
index 0000000..3eb8d55
--- /dev/null
+++ b/test/nagios/support/nagios_test.py
@@ -0,0 +1,49 @@
+import __main__ as main
+import os
+import sys
+import nagios_report
+
+def run(test):
+ """
+ run takes a function and tries it out.
+ If it returns nothing or 0 everything is fine and run prints an OK message
+ with the function name.
+ >>> def this_works_fine(): return
+ >>> run(this_works_fine)
+ 0 nagios_test.py - OK - this_works_fine
+ 0
+ >>> def this_also_works_fine(): return 0
+ >>> run(this_also_works_fine)
+ 0 nagios_test.py - OK - this_also_works_fine
+ 0
+
+ If the function returns something else it will be printed as a warning.
+ >>> run(lambda : "this is a warning")
+ 1 nagios_test.py - WARNING - this is a warning
+ 1
+
+ Errors raised will result in a CRITICAL nagios string.
+ >>> def failure(): raise Exception("something went wrong")
+ >>> run(failure)
+ 2 nagios_test.py - CRITICAL - something went wrong
+ 2
+ """
+ try:
+ name = os.path.basename(main.__file__)
+ except AttributeError:
+ name = sys.argv[0]
+ ok, warn, fail, unknown = nagios_report.functions_for_system(name)
+ try:
+ warning = test()
+ if warning and warning != 0:
+ code = warn(warning)
+ else:
+ code = ok(test.__name__)
+ except Exception as exc:
+ code = fail(exc.message or str(exc))
+ return code
+
+
+if __name__ == "__main__":
+ import doctest
+ doctest.testmod()
diff --git a/test/nagios/support/user.py b/test/nagios/support/user.py
new file mode 100644
index 0000000..9bf1d0a
--- /dev/null
+++ b/test/nagios/support/user.py
@@ -0,0 +1,64 @@
+import srp._pysrp as srp
+import binascii
+import string
+import random
+
+safe_unhexlify = lambda x: binascii.unhexlify(x) if (
+ len(x) % 2 == 0) else binascii.unhexlify('0' + x)
+
+# let's have some random name and password
+def id_generator(size=6, chars=string.ascii_lowercase + string.digits):
+ return ''.join(random.choice(chars) for x in range(size))
+
+class User():
+ def __init__(self, config = None):
+ if config and config.user:
+ self.username = config.user["username"]
+ self.password = config.user["password"]
+ else:
+ self.username = 'test_' + id_generator()
+ self.password = id_generator() + id_generator()
+ self.srp_user = srp.User(self.username, self.password, srp.SHA256, srp.NG_1024)
+
+ def signup(self, api):
+ salt, vkey = srp.create_salted_verification_key( self.username, self.password, srp.SHA256, srp.NG_1024 )
+ user_params = {
+ 'user[login]': self.username,
+ 'user[password_verifier]': binascii.hexlify(vkey),
+ 'user[password_salt]': binascii.hexlify(salt)
+ }
+ return api.post('users.json', data = user_params)
+
+ def login(self, api):
+ init=self.init_authentication(api)
+ if ('errors' in init):
+ raise Exception('test user not found')
+ auth=self.authenticate(api, init)
+ if ('errors' in auth):
+ raise Exception('srp password auth failed')
+ self.verify_server(auth)
+ if not self.is_authenticated():
+ raise Exception('user is not authenticated')
+ return auth
+
+ def init_authentication(self, api):
+ uname, A = self.srp_user.start_authentication()
+ params = {
+ 'login': uname,
+ 'A': binascii.hexlify(A)
+ }
+ return api.post('sessions', data=params)
+
+ def authenticate(self, api, init):
+ M = self.srp_user.process_challenge(
+ safe_unhexlify(init['salt']), safe_unhexlify(init['B']))
+ auth = api.put('sessions/' + self.username,
+ data={'client_auth': binascii.hexlify(M)})
+ return auth
+
+ def verify_server(self, auth):
+ self.srp_user.verify_session(safe_unhexlify(auth["M2"]))
+
+ def is_authenticated(self):
+ return self.srp_user.authenticated()
+
diff --git a/test/nagios/webapp_login.py b/test/nagios/webapp_login.py
index 1711238..7741325 100755
--- a/test/nagios/webapp_login.py
+++ b/test/nagios/webapp_login.py
@@ -2,85 +2,17 @@
# Test Authentication with the webapp API works.
-import requests
-import json
-import string
-import random
-import srp._pysrp as srp
-import binascii
-import yaml
+from support.api import Api
+from support.config import Config
+from support.user import User
-
-safe_unhexlify = lambda x: binascii.unhexlify(x) if (
- len(x) % 2 == 0) else binascii.unhexlify('0' + x)
-
-
-def read_config():
- with open("/etc/leap/hiera.yaml", 'r') as stream:
- config = yaml.load(stream)
- user = config['webapp']['nagios_test_user']
- if 'username' not in user:
- fail('nagios test user lacks username')
- if 'password' not in user:
- fail('nagios test user lacks password')
- api = config['api']
- api['version'] = config['webapp']['api_version']
- return {'api': api, 'user': user}
-
-
-def run_tests(config):
- user = config['user']
- api = config['api']
- usr = srp.User(user['username'], user['password'], srp.SHA256, srp.NG_1024)
- try:
- auth = parse(authenticate(api, usr))
- except requests.exceptions.ConnectionError:
- fail('no connection to server')
- exit(report(auth, usr))
-
-# parse the server responses
-
-
-def parse(response):
- request = response.request
- try:
- return json.loads(response.text)
- except ValueError:
- return None
-
-
-def authenticate(api, usr):
- api_url = "https://{domain}:{port}/{version}".format(**api)
- session = requests.session()
- uname, A = usr.start_authentication()
- params = {
- 'login': uname,
- 'A': binascii.hexlify(A)
- }
- init = parse(
- session.post(api_url + '/sessions', data=params, verify=False))
- if ('errors' in init):
- fail('test user not found')
- M = usr.process_challenge(
- safe_unhexlify(init['salt']), safe_unhexlify(init['B']))
- return session.put(api_url + '/sessions/' + uname, verify=False,
- data={'client_auth': binascii.hexlify(M)})
-
-
-def report(auth, usr):
- if ('errors' in auth):
- fail('srp password auth failed')
- usr.verify_session(safe_unhexlify(auth["M2"]))
- if usr.authenticated():
- print '0 webapp_login - OK - can login to webapp fine'
- return 0
- print '1 webapp_login - WARNING - failed to verify webapp server'
- return 1
-
-
-def fail(reason):
- print '2 webapp_login - CRITICAL - ' + reason
- exit(2)
+def login_successfully():
+ config = Config()
+ user = User(config)
+ api = Api(config, verify=False)
+ user.login(api)
if __name__ == '__main__':
- run_tests(read_config())
+ from support import nagios_test
+ exit_code = nagios_test.run(login_successfully)
+ exit(exit_code)
diff --git a/test/nagios/webapp_signup.py b/test/nagios/webapp_signup.py
new file mode 100755
index 0000000..3e7283e
--- /dev/null
+++ b/test/nagios/webapp_signup.py
@@ -0,0 +1,19 @@
+#!/usr/bin/env python
+
+# Test Signup and Login with the webapp API works.
+
+from support.api import Api
+from support.config import Config
+from support.user import User
+
+def signup_successfully():
+ config = Config()
+ user = User()
+ api = Api(config, verify=False)
+ user.signup(api)
+ user.login(api)
+
+if __name__ == '__main__':
+ from support import nagios_test
+ exit_code = nagios_test.run(signup_successfully)
+ exit(exit_code)
diff --git a/test/support/assert_responses.rb b/test/support/assert_responses.rb
new file mode 100644
index 0000000..b01166f
--- /dev/null
+++ b/test/support/assert_responses.rb
@@ -0,0 +1,46 @@
+module AssertResponses
+
+ # response that works with different TestCases:
+ # ActionController::TestCase has @response
+ # ActionDispatch::IntegrationTest has @response
+ # Rack::Test::Methods defines last_response
+ def get_response
+ @response || last_response
+ end
+
+ def assert_attachement_filename(name)
+ assert_equal %Q(attachment; filename="#{name}"),
+ get_response.headers["Content-Disposition"]
+ end
+
+ def json_response
+ response = JSON.parse(get_response.body)
+ response.respond_to?(:with_indifferent_access) ?
+ response.with_indifferent_access :
+ response
+ end
+
+ def assert_json_response(object)
+ assert_equal 'application/json',
+ get_response.content_type.to_s.split(';').first
+ if object.is_a? Hash
+ object.stringify_keys! if object.respond_to? :stringify_keys!
+ assert_equal object, json_response
+ else
+ assert_equal object.to_json, get_response.body
+ end
+ end
+
+ def assert_json_error(object)
+ object.stringify_keys! if object.respond_to? :stringify_keys!
+ assert_json_response :errors => object
+ end
+end
+
+class ::ActionController::TestCase
+ include AssertResponses
+end
+
+class ::ActionDispatch::IntegrationTest
+ include AssertResponses
+end
diff --git a/test/support/auth_test_helper.rb b/test/support/auth_test_helper.rb
new file mode 100644
index 0000000..57f9f9b
--- /dev/null
+++ b/test/support/auth_test_helper.rb
@@ -0,0 +1,65 @@
+module AuthTestHelper
+ extend ActiveSupport::Concern
+
+ # Controller will fetch current user from warden.
+ # Make it pick up our current_user
+ included do
+ setup do
+ request.env['warden'] ||= stub :user => nil
+ end
+ end
+
+ def login(user_or_method_hash = {})
+ if user_or_method_hash.respond_to?(:reverse_merge)
+ user_or_method_hash.reverse_merge! :is_admin? => false
+ end
+ @current_user = stub_record(:user, user_or_method_hash)
+ request.env['warden'] = stub :user => @current_user
+ request.env['HTTP_AUTHORIZATION'] = header_for_token_auth
+ return @current_user
+ end
+
+ def assert_access_denied(denied = true, logged_in = true)
+ if denied
+ if @response.content_type == 'application/json'
+ assert_json_response('error' => I18n.t(:not_authorized))
+ assert_response :unprocessable_entity
+ else
+ if logged_in
+ assert_equal({:alert => I18n.t(:not_authorized)}, flash.to_hash)
+ assert_redirected_to home_url
+ else
+ assert_equal({:alert => I18n.t(:not_authorized_login)}, flash.to_hash)
+ assert_redirected_to login_url
+ end
+ end
+ else
+ assert flash[:alert].blank?
+ end
+ end
+
+ def expect_logout
+ expect_warden_logout
+ @token.expects(:destroy) if @token
+ end
+
+ protected
+
+ def header_for_token_auth
+ @token = find_record(:token, :authenticate => @current_user)
+ ActionController::HttpAuthentication::Token.encode_credentials @token.id
+ end
+
+ def expect_warden_logout
+ raw = mock('raw session') do
+ expects(:inspect)
+ end
+ request.env['warden'].expects(:raw_session).returns(raw)
+ request.env['warden'].expects(:logout)
+ end
+
+end
+
+class ActionController::TestCase
+ include AuthTestHelper
+end
diff --git a/test/support/browser_integration_test.rb b/test/support/browser_integration_test.rb
new file mode 100644
index 0000000..1c872ff
--- /dev/null
+++ b/test/support/browser_integration_test.rb
@@ -0,0 +1,98 @@
+#
+# BrowserIntegrationTest
+#
+# Use this class for capybara based integration tests for the ui.
+#
+
+class BrowserIntegrationTest < ActionDispatch::IntegrationTest
+
+ CONFIG_RU = (Rails.root + 'config.ru').to_s
+ OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first
+
+ require 'capybara/poltergeist'
+
+ Capybara.register_driver :rack_test do |app|
+ Capybara::RackTest::Driver.new(app)
+ end
+
+ Capybara.register_driver :poltergeist do |app|
+ Capybara::Poltergeist::Driver.new(app)
+ end
+
+ # this is integration testing. So let's make the whole
+ # rack stack available...
+ Capybara.app = OUTER_APP
+ Capybara.run_server = true
+ Capybara.app_host = 'http://lvh.me:3003'
+ Capybara.server_port = 3003
+ Capybara.javascript_driver = :poltergeist
+ Capybara.default_wait_time = 5
+
+
+ # Make the Capybara DSL available
+ include Capybara::DSL
+
+ setup do
+ Capybara.current_driver = Capybara.javascript_driver
+ page.driver.add_headers 'ACCEPT-LANGUAGE' => 'en-EN'
+ end
+
+ teardown do
+ Capybara.reset_sessions! # Forget the (simulated) browser state
+ Capybara.use_default_driver # Revert Capybara.current_driver to Capybara.default_driver
+ end
+
+ def submit_signup(username = nil, password = nil)
+ username ||= "test_#{SecureRandom.urlsafe_base64}".downcase
+ password ||= SecureRandom.base64
+ visit '/users/new'
+ fill_in 'Username', with: username
+ fill_in 'Password', with: password
+ fill_in 'Password confirmation', with: password
+ click_on 'Sign Up'
+ return username, password
+ end
+
+ # currently this only works for tests with poltergeist.
+ def login(user = nil)
+ @user ||= user ||= FactoryGirl.create(:user)
+ token = Token.create user_id: user.id
+ page.driver.add_header "Authorization", %Q(Token token="#{token}")
+ visit '/'
+ end
+
+ teardown do
+ if @user && @user.reload
+ Identity.destroy_all_for @user
+ @user.destroy
+ end
+ end
+
+ add_teardown_hook do |testcase|
+ unless testcase.passed?
+ testcase.save_state
+ end
+ end
+
+ def save_state
+ File.open(logfile_path, 'w') do |test_log|
+ test_log.puts self.class.name
+ test_log.puts "========================="
+ test_log.puts __name__
+ test_log.puts Time.now
+ test_log.puts current_path
+ test_log.puts page.status_code
+ test_log.puts page.response_headers
+ test_log.puts "page.html"
+ test_log.puts "------------------------"
+ test_log.puts page.html
+ test_log.puts "server log"
+ test_log.puts "------------------------"
+ test_log.puts `tail log/test.log -n 200`
+ end
+ page.save_screenshot screenshot_path
+ # some drivers do not support screenshots
+ rescue Capybara::NotSupportedByDriverError
+ end
+
+end
diff --git a/test/support/rack_test.rb b/test/support/rack_test.rb
new file mode 100644
index 0000000..806339a
--- /dev/null
+++ b/test/support/rack_test.rb
@@ -0,0 +1,38 @@
+require_relative 'assert_responses'
+
+class RackTest < ActiveSupport::TestCase
+ include Rack::Test::Methods
+ include Warden::Test::Helpers
+
+ CONFIG_RU = (Rails.root + 'config.ru').to_s
+ OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first
+
+ def app
+ OUTER_APP
+ end
+
+ def assert_access_denied
+ assert_json_response('error' => I18n.t(:not_authorized))
+ assert_response :unprocessable_entity
+ end
+
+ # inspired by rails 4
+ # -> actionpack/lib/action_dispatch/testing/assertions/response.rb
+ def assert_response(type, message = nil)
+ # RackTest does not know @response
+ response_code = last_response.status
+ message ||= "Expected response to be a <#{type}>, but was <#{response_code}>"
+
+ if Symbol === type
+ if [:success, :missing, :redirect, :error].include?(type)
+ assert last_response.send("#{type}?"), message
+ else
+ code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
+ assert_equal code, response_code, message
+ end
+ else
+ assert_equal type, response_code, message
+ end
+ end
+
+end
diff --git a/test/support/stub_record_helper.rb b/test/support/stub_record_helper.rb
new file mode 100644
index 0000000..25138a0
--- /dev/null
+++ b/test/support/stub_record_helper.rb
@@ -0,0 +1,53 @@
+module StubRecordHelper
+
+ #
+ # We will stub find when called on the records class and
+ # return the record given.
+ #
+ # If no record is given but a hash or nil will create a stub based on
+ # that instead and returns the stub.
+ #
+ def find_record(factory, record_or_attribs_hash = {})
+ record = stub_record factory, record_or_attribs_hash, true
+ klass = record.class
+ # find is just an alias for get with CouchRest Model
+ klass.stubs(:get).with(record.to_param.to_s).returns(record)
+ klass.stubs(:find).with(record.to_param.to_s).returns(record)
+ return record
+ end
+
+ # Create a stub that has the usual functions of a database record.
+ # It won't fail on rendering a form for example.
+ #
+ # If the second parameter is a record we return the record itself.
+ # This way you can build functions that either take a record or a
+ # method hash to stub from. See find_record for an example.
+ def stub_record(factory, record_or_method_hash = {}, persisted=false)
+ if record_or_method_hash && !record_or_method_hash.is_a?(Hash)
+ return record_or_method_hash
+ end
+ FactoryGirl.build_stubbed(factory).tap do |record|
+ if persisted or record.persisted?
+ record_or_method_hash.reverse_merge! :created_at => Time.now,
+ :updated_at => Time.now, :id => Random.rand(100000).to_s
+ end
+ record.stubs(record_or_method_hash) if record_or_method_hash.present?
+ end
+ end
+
+ # returns deep stringified attributes so they can be compared to
+ # what the controller receives as params
+ def record_attributes_for(factory, attribs_hash = nil)
+ FactoryGirl.attributes_for(factory, attribs_hash).tap do |attribs|
+ attribs.keys.each do |key|
+ val = attribs.delete(key)
+ attribs[key.to_s] = val.is_a?(Hash) ? val.stringify_keys! : val
+ end
+ end
+ end
+
+end
+
+class ActionController::TestCase
+ include StubRecordHelper
+end
diff --git a/test/support/time_test_helper.rb b/test/support/time_test_helper.rb
new file mode 100644
index 0000000..f673f12
--- /dev/null
+++ b/test/support/time_test_helper.rb
@@ -0,0 +1,30 @@
+# Extend the Time class so that we can offset the time that 'now'
+# returns. This should allow us to effectively time warp for functional
+# tests that require limits per hour, what not.
+class Time #:nodoc:
+ class <<self
+ attr_accessor :testing_offset
+
+ def now_with_testing_offset
+ now_without_testing_offset - testing_offset
+ end
+ alias_method_chain :now, :testing_offset
+ end
+end
+Time.testing_offset = 0
+
+module TimeTestHelper
+ # Time warp to the specified time for the duration of the passed block
+ def pretend_now_is(time)
+ begin
+ Time.testing_offset = Time.now - time
+ yield
+ ensure
+ Time.testing_offset = 0
+ end
+ end
+end
+
+class ActiveSupport::TestCase
+ include TimeTestHelper
+end
diff --git a/test/support/with_config_helper.rb b/test/support/with_config_helper.rb
new file mode 100644
index 0000000..65eb7bc
--- /dev/null
+++ b/test/support/with_config_helper.rb
@@ -0,0 +1,16 @@
+module WithConfigHelper
+ extend ActiveSupport::Concern
+
+ def with_config(options)
+ old_config = APP_CONFIG.dup
+ APP_CONFIG.merge! options
+ yield
+ ensure
+ APP_CONFIG.replace old_config
+ end
+
+end
+
+class ActiveSupport::TestCase
+ include WithConfigHelper
+end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 3fb2716..d001ac7 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -4,80 +4,15 @@ require 'rails/test_help'
require 'mocha/setup'
+# Load support files from toplevel
+Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
+
# Load support files from all engines
-Dir["#{File.dirname(__FILE__)}/../*/test/support/**/*.rb"].each { |f| require f }
+Dir["#{File.dirname(__FILE__)}/../engines/*/test/support/**/*.rb"].each { |f| require f }
class ActiveSupport::TestCase
# Add more helper methods to be used by all tests here...
- def file_path(name)
- File.join(Rails.root, 'test', 'files', name)
- end
-
-end
-
-require 'capybara/poltergeist'
-
-CONFIG_RU = (Rails.root + 'config.ru').to_s
-OUTER_APP = Rack::Builder.parse_file(CONFIG_RU).first
-
-Capybara.register_driver :rack_test do |app|
- Capybara::RackTest::Driver.new(app)
-end
-
-Capybara.register_driver :poltergeist do |app|
- Capybara::Poltergeist::Driver.new(app)
-end
-
-# this is integration testing. So let's make the whole
-# rack stack available...
-Capybara.app = OUTER_APP
-Capybara.run_server = true
-Capybara.app_host = 'http://lvh.me:3003'
-Capybara.server_port = 3003
-Capybara.javascript_driver = :poltergeist
-Capybara.default_wait_time = 5
-
-class BrowserIntegrationTest < ActionDispatch::IntegrationTest
- # Make the Capybara DSL available
- include Capybara::DSL
- include IntegrationTestHelper
-
- setup do
- Capybara.current_driver = Capybara.javascript_driver
- page.driver.add_headers 'ACCEPT-LANGUAGE' => 'en-EN'
- end
-
- teardown do
- Capybara.reset_sessions! # Forget the (simulated) browser state
- Capybara.use_default_driver # Revert Capybara.current_driver to Capybara.default_driver
- end
-
- add_teardown_hook do |testcase|
- unless testcase.passed?
- testcase.save_state
- end
- end
-
- def save_state
- page.save_screenshot screenshot_path
- File.open(logfile_path, 'w') do |test_log|
- test_log.puts self.class.name
- test_log.puts "========================="
- test_log.puts __name__
- test_log.puts Time.now
- test_log.puts current_path
- test_log.puts page.status_code
- test_log.puts page.response_headers
- test_log.puts "page.html"
- test_log.puts "------------------------"
- test_log.puts page.html
- test_log.puts "server log"
- test_log.puts "------------------------"
- test_log.puts `tail log/test.log -n 200`
- end
- end
-
protected
def logfile_path
@@ -87,4 +22,9 @@ class BrowserIntegrationTest < ActionDispatch::IntegrationTest
def screenshot_path
Rails.root + 'tmp' + "#{self.class.name.underscore}.#{__name__}.png"
end
+
+ def file_path(name)
+ File.join(Rails.root, 'test', 'files', name)
+ end
+
end
diff --git a/test/unit/account_test.rb b/test/unit/account_test.rb
new file mode 100644
index 0000000..b2bfe27
--- /dev/null
+++ b/test/unit/account_test.rb
@@ -0,0 +1,47 @@
+require 'test_helper'
+
+class AccountTest < ActiveSupport::TestCase
+
+ teardown do
+ Identity.destroy_all_disabled
+ end
+
+ test "create a new account" do
+ user = Account.create(FactoryGirl.attributes_for(:user))
+ assert user.valid?, "unexpected errors: #{user.errors.inspect}"
+ assert user.persisted?
+ assert id = user.identity
+ assert_equal user.email_address, id.address
+ assert_equal user.email_address, id.destination
+ user.account.destroy
+ end
+
+ test "create and remove a user account" do
+ # We keep an identity that will block the handle from being reused.
+ assert_difference "Identity.count" do
+ assert_no_difference "User.count" do
+ user = Account.create(FactoryGirl.attributes_for(:user))
+ user.account.destroy
+ end
+ end
+ end
+
+ test "change username and create alias" do
+ user = Account.create(FactoryGirl.attributes_for(:user))
+ old_id = user.identity
+ old_email = user.email_address
+ user.account.update(FactoryGirl.attributes_for(:user))
+ user.reload
+ old_id.reload
+ assert user.valid?
+ assert user.persisted?
+ assert id = user.identity
+ assert id.persisted?
+ assert_equal user.email_address, id.address
+ assert_equal user.email_address, id.destination
+ assert_equal user.email_address, old_id.destination
+ assert_equal old_email, old_id.address
+ user.account.destroy
+ end
+
+end
diff --git a/test/unit/anonymous_user_test.rb b/test/unit/anonymous_user_test.rb
new file mode 100644
index 0000000..6e94d39
--- /dev/null
+++ b/test/unit/anonymous_user_test.rb
@@ -0,0 +1,23 @@
+require 'test_helper'
+
+class AnonymousUserTest < ActiveSupport::TestCase
+
+ setup do
+ @anonymous = AnonymousUser.new
+ end
+
+ test "has nil values" do
+ assert_nil @anonymous.id
+ assert_nil @anonymous.email_address
+ assert_nil @anonymous.login
+ end
+
+ test "has no messages" do
+ assert_equal [], @anonymous.messages
+ end
+
+ test "has anonymous service level" do
+ assert @anonymous.effective_service_level.is_a? AnonymousServiceLevel
+ end
+
+end
diff --git a/test/unit/client_certificate_test.rb b/test/unit/client_certificate_test.rb
new file mode 100644
index 0000000..036e724
--- /dev/null
+++ b/test/unit/client_certificate_test.rb
@@ -0,0 +1,24 @@
+require 'test_helper'
+
+class ClientCertificateTest < ActiveSupport::TestCase
+
+ test "new cert has all we need" do
+ sample = ClientCertificate.new
+ assert sample.key
+ assert sample.cert
+ assert sample.to_s
+ end
+
+ test "cert has configured prefix" do
+ prefix = "PREFIX"
+ sample = ClientCertificate.new(:prefix => prefix)
+ assert sample.cert.subject.common_name.starts_with?(prefix)
+ end
+
+ test "cert issuer matches ca subject" do
+ sample = ClientCertificate.new
+ cert = OpenSSL::X509::Certificate.new(sample.cert.to_pem)
+ assert_equal ClientCertificate.root_ca.openssl_body.subject, cert.issuer
+ end
+
+end
diff --git a/test/unit/helpers/session_helper_test.rb b/test/unit/helpers/session_helper_test.rb
new file mode 100644
index 0000000..2824733
--- /dev/null
+++ b/test/unit/helpers/session_helper_test.rb
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class SessionHelperTest < ActionView::TestCase
+end
diff --git a/test/unit/helpers/users_helper_test.rb b/test/unit/helpers/users_helper_test.rb
new file mode 100644
index 0000000..96af37a
--- /dev/null
+++ b/test/unit/helpers/users_helper_test.rb
@@ -0,0 +1,4 @@
+require 'test_helper'
+
+class UsersHelperTest < ActionView::TestCase
+end
diff --git a/test/unit/identity_test.rb b/test/unit/identity_test.rb
new file mode 100644
index 0000000..eca104f
--- /dev/null
+++ b/test/unit/identity_test.rb
@@ -0,0 +1,133 @@
+require 'test_helper'
+
+class IdentityTest < ActiveSupport::TestCase
+ include StubRecordHelper
+
+ setup do
+ @user = find_record :user
+ end
+
+ test "initial identity for a user" do
+ id = Identity.for(@user)
+ assert_equal @user.email_address, id.address
+ assert_equal @user.email_address, id.destination
+ assert_equal @user, id.user
+ end
+
+ test "add alias" do
+ id = Identity.for @user, address: alias_name
+ assert_equal LocalEmail.new(alias_name), id.address
+ assert_equal @user.email_address, id.destination
+ assert_equal @user, id.user
+ end
+
+ test "add forward" do
+ id = Identity.for @user, destination: forward_address
+ assert_equal @user.email_address, id.address
+ assert_equal Email.new(forward_address), id.destination
+ assert_equal @user, id.user
+ end
+
+ test "forward alias" do
+ id = Identity.for @user, address: alias_name, destination: forward_address
+ assert_equal LocalEmail.new(alias_name), id.address
+ assert_equal Email.new(forward_address), id.destination
+ assert_equal @user, id.user
+ end
+
+ test "prevents duplicates" do
+ id = Identity.create_for @user, address: alias_name, destination: forward_address
+ dup = Identity.build_for @user, address: alias_name, destination: forward_address
+ assert !dup.valid?
+ assert_equal ["This alias already exists"], dup.errors[:base]
+ id.destroy
+ end
+
+ test "validates availability" do
+ other_user = find_record :user
+ id = Identity.create_for @user, address: alias_name, destination: forward_address
+ taken = Identity.build_for other_user, address: alias_name
+ assert !taken.valid?
+ assert_equal ["This email has already been taken"], taken.errors[:base]
+ id.destroy
+ end
+
+ test "setting and getting pgp key" do
+ id = Identity.for(@user)
+ id.set_key(:pgp, pgp_key_string)
+ assert_equal pgp_key_string, id.keys[:pgp]
+ end
+
+ test "querying pgp key via couch" do
+ id = Identity.for(@user)
+ id.set_key(:pgp, pgp_key_string)
+ id.save
+ view = Identity.pgp_key_by_email.key(id.address)
+ assert_equal 1, view.rows.count
+ assert result = view.rows.first
+ assert_equal id.address, result["key"]
+ assert_equal id.keys[:pgp], result["value"]
+ id.destroy
+ end
+
+ test "fail to add non-local email address as identity address" do
+ id = Identity.for @user, address: forward_address
+ assert !id.valid?
+ assert_match /needs to end in/, id.errors[:address].first
+ end
+
+ test "alias must meet same conditions as login" do
+ id = Identity.create_for @user, address: alias_name.capitalize
+ assert !id.valid?
+ #hacky way to do this, but okay for now:
+ assert id.errors.messages.flatten(2).include? "Must begin with a lowercase letter"
+ assert id.errors.messages.flatten(2).include? "Only lowercase letters, digits, . - and _ allowed."
+ end
+
+ test "destination must be valid email address" do
+ id = Identity.create_for @user, address: @user.email_address, destination: 'ASKJDLFJD'
+ assert !id.valid?
+ assert id.errors.messages[:destination].include? "needs to be a valid email address"
+ end
+
+ test "disabled identity" do
+ id = Identity.for(@user)
+ id.disable
+ assert_equal @user.email_address, id.address
+ assert_equal nil, id.destination
+ assert_equal nil, id.user
+ assert !id.enabled?
+ assert id.valid?
+ end
+
+ test "disabled identity blocks handle" do
+ id = Identity.for(@user)
+ id.disable
+ id.save
+ other_user = find_record :user
+ taken = Identity.build_for other_user, address: id.address
+ assert !taken.valid?
+ Identity.destroy_all_disabled
+ end
+
+ test "destroy all disabled identities" do
+ id = Identity.for(@user)
+ id.disable
+ id.save
+ assert Identity.count > 0
+ Identity.destroy_all_disabled
+ assert_equal 0, Identity.disabled.count
+ end
+
+ def alias_name
+ @alias_name ||= Faker::Internet.user_name
+ end
+
+ def forward_address
+ @forward_address ||= Faker::Internet.email
+ end
+
+ def pgp_key_string
+ @pgp_key ||= "DUMMY PGP KEY ... "+SecureRandom.base64(4096)
+ end
+end
diff --git a/test/unit/local_email_test.rb b/test/unit/local_email_test.rb
new file mode 100644
index 0000000..20ee7f1
--- /dev/null
+++ b/test/unit/local_email_test.rb
@@ -0,0 +1,65 @@
+require 'test_helper'
+
+class LocalEmailTest < ActiveSupport::TestCase
+
+ test "appends domain" do
+ local = LocalEmail.new(handle)
+ assert_equal LocalEmail.new(email), local
+ assert local.valid?
+ end
+
+ test "returns handle" do
+ local = LocalEmail.new(email)
+ assert_equal handle, local.handle
+ end
+
+ test "prints full email" do
+ local = LocalEmail.new(handle)
+ assert_equal email, "#{local}"
+ end
+
+ test "validates domain" do
+ local = LocalEmail.new(Faker::Internet.email)
+ assert !local.valid?
+ assert_equal ["needs to end in @#{LocalEmail.domain}"], local.errors[:email]
+ end
+
+ test "blacklists rfc2142" do
+ black_listed = LocalEmail.new('hostmaster')
+ assert !black_listed.valid?
+ end
+
+ test "blacklists etc passwd" do
+ black_listed = LocalEmail.new('nobody')
+ assert !black_listed.valid?
+ end
+
+ test "whitelist overwrites automatic blacklists" do
+ with_config handle_whitelist: ['nobody', 'hostmaster'] do
+ white_listed = LocalEmail.new('nobody')
+ assert white_listed.valid?
+ white_listed = LocalEmail.new('hostmaster')
+ assert white_listed.valid?
+ end
+ end
+
+ test "blacklists from config" do
+ black_listed = LocalEmail.new('www-data')
+ assert !black_listed.valid?
+ end
+
+ test "blacklist from config overwrites whitelist" do
+ with_config handle_whitelist: ['www-data'] do
+ black_listed = LocalEmail.new('www-data')
+ assert !black_listed.valid?
+ end
+ end
+
+ def handle
+ @handle ||= Faker::Internet.user_name
+ end
+
+ def email
+ handle + "@" + APP_CONFIG[:domain]
+ end
+end
diff --git a/test/unit/token_test.rb b/test/unit/token_test.rb
new file mode 100644
index 0000000..a3c6cf6
--- /dev/null
+++ b/test/unit/token_test.rb
@@ -0,0 +1,89 @@
+require 'test_helper'
+
+class ClientCertificateTest < ActiveSupport::TestCase
+ include StubRecordHelper
+
+ setup do
+ @user = find_record :user
+ end
+
+ test "new token for user" do
+ sample = Token.new(:user_id => @user.id)
+ assert sample.valid?
+ assert_equal @user.id, sample.user_id
+ assert_equal @user, sample.authenticate
+ end
+
+ test "token id is secure" do
+ sample = Token.new(:user_id => @user.id)
+ other = Token.new(:user_id => @user.id)
+ assert sample.id,
+ "id is set on initialization"
+ assert sample.id[0..10] != other.id[0..10],
+ "token id prefixes should not repeat"
+ assert /[g-zG-Z]/.match(sample.id),
+ "should use non hex chars in the token id"
+ assert sample.id.size > 16,
+ "token id should be more than 16 chars long"
+ end
+
+ test "token checks for user" do
+ sample = Token.new
+ assert !sample.valid?, "Token should require a user record"
+ end
+
+ test "token updates timestamps" do
+ sample = Token.new(user_id: @user.id)
+ sample.last_seen_at = 1.minute.ago
+ sample.expects(:save)
+ assert_equal @user, sample.authenticate
+ assert Time.now - sample.last_seen_at < 1.minute, "last_seen_at has not been updated"
+ end
+
+ test "token will not expire if token_expires_after is not set" do
+ sample = Token.new(user_id: @user.id)
+ sample.last_seen_at = 2.years.ago
+ with_config auth: {} do
+ sample.expects(:save)
+ assert_equal @user, sample.authenticate
+ end
+ end
+
+ test "expired token returns nil on authenticate" do
+ sample = Token.new(user_id: @user.id)
+ sample.last_seen_at = 2.hours.ago
+ with_config auth: {token_expires_after: 60} do
+ sample.expects(:destroy)
+ assert_nil sample.authenticate
+ end
+ end
+
+ test "Token.destroy_all_expired is noop if no expiry is set" do
+ expired = FactoryGirl.create :token, last_seen_at: 2.hours.ago
+ with_config auth: {} do
+ Token.destroy_all_expired
+ end
+ assert_equal expired, Token.find(expired.id)
+ end
+
+ test "Token.destroy_all_expired cleans up expired tokens only" do
+ expired = FactoryGirl.create :token, last_seen_at: 2.hours.ago
+ fresh = FactoryGirl.create :token
+ with_config auth: {token_expires_after: 60} do
+ Token.destroy_all_expired
+ end
+ assert_nil Token.find(expired.id)
+ assert_equal fresh, Token.find(fresh.id)
+ fresh.destroy
+ end
+
+
+ test "Token.destroy_all_expired does not interfere with expired.authenticate" do
+ expired = FactoryGirl.create :token, last_seen_at: 2.hours.ago
+ with_config auth: {token_expires_after: 60} do
+ Token.destroy_all_expired
+ end
+ assert_nil expired.authenticate
+ end
+
+end
diff --git a/test/unit/user_test.rb b/test/unit/user_test.rb
new file mode 100644
index 0000000..ffbb7d8
--- /dev/null
+++ b/test/unit/user_test.rb
@@ -0,0 +1,68 @@
+require 'test_helper'
+
+class UserTest < ActiveSupport::TestCase
+
+ include SRP::Util
+ setup do
+ @user = FactoryGirl.build(:user)
+ end
+
+ test "design docs in database are authorative" do
+ assert !User.design_doc.auto_update,
+ "Automatic update of design docs should be disabled"
+ end
+
+ test "test set of attributes should be valid" do
+ @user.valid?
+ assert_equal Hash.new, @user.errors.messages
+ end
+
+ test "test require hex for password_verifier" do
+ @user.password_verifier = "QWER"
+ assert !@user.valid?
+ end
+
+ test "test require alphanumerical for login" do
+ @user.login = "qw#r"
+ assert !@user.valid?
+ end
+
+ test "verifier returns number for the hex in password_verifier" do
+ assert_equal @user.password_verifier.hex, @user.verifier
+ end
+
+ test "salt returns number for the hex in password_salt" do
+ assert_equal @user.password_salt.hex, @user.salt
+ end
+
+ test 'normal user is no admin' do
+ assert !@user.is_admin?
+ end
+
+ test 'user with login in APP_CONFIG is an admin' do
+ admin_login = APP_CONFIG['admins'].first
+ @user.login = admin_login
+ assert @user.is_admin?
+ end
+
+ test "login needs to be unique" do
+ other_user = FactoryGirl.create :user, login: @user.login
+ assert !@user.valid?
+ other_user.destroy
+ end
+
+ test "login needs to be unique amongst aliases" do
+ other_user = FactoryGirl.create :user
+ id = Identity.create_for other_user, address: @user.login
+ assert !@user.valid?
+ id.destroy
+ other_user.destroy
+ end
+
+ test "deprecated public key api still works" do
+ key = SecureRandom.base64(4096)
+ @user.public_key = key
+ assert_equal key, @user.public_key
+ end
+
+end
diff --git a/test/unit/warden_strategy_secure_remote_password_test.rb b/test/unit/warden_strategy_secure_remote_password_test.rb
new file mode 100644
index 0000000..e6fcfbe
--- /dev/null
+++ b/test/unit/warden_strategy_secure_remote_password_test.rb
@@ -0,0 +1,63 @@
+class WardenStrategySecureRemotePasswordTest < ActiveSupport::TestCase
+
+# TODO : turn this into sth. real
+=begin
+ setup do
+ @user = stub :login => "me", :id => 123
+ @client_hex = 'a123'
+ @client_rnd = @client_hex.hex
+ @server_hex = 'b123'
+ @server_rnd = @server_hex.hex
+ @server_rnd_exp = 'e123'.hex
+ @salt = 'stub user salt'
+ @server_handshake = stub :aa => @client_rnd, :bb => @server_rnd, :b => @server_rnd_exp
+ @server_auth = 'adfe'
+ end
+
+
+ test "should perform handshake" do
+ @user.expects(:initialize_auth).
+ with(@client_rnd).
+ returns(@server_handshake)
+ @server_handshake.expects(:to_json).
+ returns({'B' => @server_hex, 'salt' => @salt}.to_json)
+ User.expects(:find).with(@user.login).returns(@user)
+ assert_equal @server_handshake, session[:handshake]
+ assert_response :success
+ assert_json_response :B => @server_hex, :salt => @salt
+ end
+
+ test "should report user not found" do
+ unknown = "login_that_does_not_exist"
+ User.expects(:find).with(unknown).raises(RECORD_NOT_FOUND)
+ post :create, :login => unknown
+ assert_response :success
+ assert_json_error "login" => ["unknown user"]
+ end
+
+ test "should authorize" do
+ session[:handshake] = @server_handshake
+ @server_handshake.expects(:authenticate!).
+ with(@client_rnd).
+ returns(@user)
+ @server_handshake.expects(:to_json).
+ returns({:M2 => @server_auth}.to_json)
+ post :update, :id => @user.login, :client_auth => @client_hex
+ assert_nil session[:handshake]
+ assert_json_response :M2 => @server_auth
+ assert_equal @user.id, session[:user_id]
+ end
+
+ test "should report wrong password" do
+ session[:handshake] = @server_handshake
+ @server_handshake.expects(:authenticate!).
+ with(@client_rnd).
+ raises(WRONG_PASSWORD)
+ post :update, :id => @user.login, :client_auth => @client_hex
+ assert_nil session[:handshake]
+ assert_nil session[:user_id]
+ assert_json_error "password" => ["wrong password"]
+ end
+
+=end
+end
diff --git a/test/unit/webfinger/host_meta_presenter_test.rb b/test/unit/webfinger/host_meta_presenter_test.rb
new file mode 100644
index 0000000..af86404
--- /dev/null
+++ b/test/unit/webfinger/host_meta_presenter_test.rb
@@ -0,0 +1,24 @@
+require 'test_helper'
+require 'webfinger'
+require 'json'
+
+class Webfinger::HostMetaPresenterTest < ActiveSupport::TestCase
+
+ setup do
+ @request = stub(
+ url: "https://#{APP_CONFIG[:domain]}/.well-known/host-meta"
+ )
+ @meta = Webfinger::HostMetaPresenter.new(@request)
+ end
+
+ test "creates proper json" do
+ hash = JSON.parse @meta.to_json
+ assert_equal ["subject", "links"].sort, hash.keys.sort
+ hash.each do |key, value|
+ assert_equal @meta.send(key.to_sym).to_json, value.to_json
+ end
+ end
+
+end
+
+
diff --git a/test/unit/webfinger/user_presenter_test.rb b/test/unit/webfinger/user_presenter_test.rb
new file mode 100644
index 0000000..04aeb22
--- /dev/null
+++ b/test/unit/webfinger/user_presenter_test.rb
@@ -0,0 +1,49 @@
+require 'test_helper'
+require 'webfinger'
+require 'json'
+
+class Webfinger::UserPresenterTest < ActiveSupport::TestCase
+
+
+ setup do
+ @user = stub(
+ username: 'testuser',
+ email_address: "testuser@#{APP_CONFIG[:domain]}"
+ )
+ @request = stub(
+ host: APP_CONFIG[:domain]
+ )
+ end
+
+ test "user without key has no links" do
+ @user.stubs :public_key => nil
+ presenter = Webfinger::UserPresenter.new(@user, @request)
+ assert_equal Hash.new, presenter.links
+ end
+
+ test "user with key has corresponding link" do
+ @user.stubs :public_key => "here's a key"
+ presenter = Webfinger::UserPresenter.new(@user, @request)
+ assert_equal [:public_key], presenter.links.keys
+ assert_equal "PGP", presenter.links[:public_key][:type]
+ assert_equal presenter.send(:key), presenter.links[:public_key][:href]
+ end
+
+ test "key is base64 encoded" do
+ @user.stubs :public_key => "here's a key"
+ presenter = Webfinger::UserPresenter.new(@user, @request)
+ assert_equal Base64.encode64(@user.public_key), presenter.send(:key)
+ end
+
+ test "creates proper json representation" do
+ @user.stubs :public_key => "here's a key"
+ presenter = Webfinger::UserPresenter.new(@user, @request)
+ hash = JSON.parse presenter.to_json
+ assert_equal ["subject", "links"].sort, hash.keys.sort
+ hash.each do |key, value|
+ assert_equal presenter.send(key.to_sym).to_json, value.to_json
+ end
+ end
+
+
+end