summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorAlexey Shchepin <alexey@process-one.net>2016-03-31 14:53:31 +0300
committerAlexey Shchepin <alexey@process-one.net>2016-03-31 14:53:31 +0300
commit3dc55c6d47e3093a6147ce275c7269a7d08ffc45 (patch)
tree1ff7eb63244a18f9c91dc26dd6e6845499f9f5b5 /test
parentmix version updated for 16.03 release (diff)
Commands refactor, first pass.
- add API versionning - changed error handling, based on exception - commands moved/merged from mod_admin_p1 to mod_admin_extra - command bufixes - add some elixir unit test cases Squashed commit of the following: commit dd59855b3486f78a9349756e4f102e79b3accff8 Merge: 14e8ffc 506e08e Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Fri Oct 30 11:43:18 2015 +0100 Merge branch '3.2.x' into api commit 14e8ffce78cbea6c8605371d1fc50a0c1d1e012c Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Tue Oct 27 16:35:17 2015 +0100 Added OAuth tests to ejabberd_commands commit f81c550c14628edfe4861c228576cb767924366a Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Tue Oct 27 16:34:55 2015 +0100 Added some mod_http_api tests commit 6a64578d5b2ba532a2feb6503ed98561e56d5d53 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Mon Oct 26 15:29:36 2015 +0100 Fix get_last command test Previous version won't work with dst. commit 27e0cde9e9c1f001effe68f8424a365ad947c068 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Fri Oct 23 17:59:34 2015 +0200 Add tests on admin command policy commit 19dad8d54f54c9fabd454280483cccfb06c8e78a Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Fri Oct 23 16:49:36 2015 +0200 Added command related tests (http api & user policy) commit e0e596ab4a3f3a70aba5f374f028939ab794de33 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Fri Oct 23 16:49:16 2015 +0200 Fix command call. commit 128cd7d1ede3c47a34f8ec3a750c980ccad2c61d Merge: 60c4c4c 447313c Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Thu Oct 22 14:48:39 2015 +0200 Merge branch '3.2.x' into api commit 60c4c4c0751302524c14219c6bc8c56a6069a689 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Thu Oct 22 14:45:57 2015 +0200 Fix ejabberd_commands spec. commit 8e145c28c5da762c2b93ee32327eff1db94ebfed Merge: 397273a f13dc94 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Wed Oct 21 18:26:07 2015 +0200 Merge branch '3.2.x' into api commit 397273a23ed415feac87aed33da6452229793387 Merge: c30e89b f289e27 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Wed Oct 21 15:27:45 2015 +0200 Merge branch '3.2.x' into api commit c30e89bb8a0013bff37e61e4c6953350c9c1f313 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Wed Oct 21 12:47:02 2015 +0200 Merge mod_http_api commit 7b0db22b4acd48ff6fabce41c1b2525e6580a3c5 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Fri Oct 16 11:55:48 2015 +0200 Fix exunit tests to run with common_test suites commit d8b1a89800ac7379a57a7eb4a09c3c93c3e1e5eb Merge: 2879ae8 63455b3 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Thu Oct 15 11:39:45 2015 +0200 Merge branch '3.2.x' into api commit 2879ae87ff3eee369ef3d780136b96ecff5285d1 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Wed Oct 14 14:53:44 2015 +0200 Fix update_roster command. commit a1d453dd7a3afda9861a8d747494a45057ad574b Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Tue Oct 13 16:14:28 2015 +0200 API commands refactor Moving and/or merging commands from mod_admin_p1 to mod_admin_extra commit b709ed26b0fc0ca4f3bdd5a59fa58ec7e3db97fa Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Wed Oct 7 15:10:01 2015 +0200 Add tests on commands commit 6711687bee9c672cb3d5aed0744e13420ecf6dbd Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Tue Sep 29 15:58:16 2015 +0200 Add ejabberd_commands tests commit df8682f419cf3877e77e36a19bca0fc55dc991f8 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Mon Sep 28 14:54:39 2015 +0200 Added API versioning for ejabberdctl and rest commands commit cd017b0e3aac431bc3ee807ceb7f8641e1523ef5 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Fri Sep 18 11:21:45 2015 +0200 Better error handling of HTTP API commands. commit ca5cb6acd8e4643f9d6c484d2277b0d7e88471e5 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Tue Sep 15 15:03:05 2015 +0200 add commands to mod_admin_extra: - get_offline_count - get_presence - change_password commit 7f583fa099e30ac2b0915669fd8f102ac565b833 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Tue Sep 15 15:02:16 2015 +0200 Improve REST API error handling commit 14753b1c02cdce434a786b7f80f6c09f0d210075 Author: Jerome Sautret <jerome.sautret@process-one.net> Date: Mon Sep 14 10:51:17 2015 +0200 Change REST API return codes for integer type.
Diffstat (limited to 'test')
-rw-r--r--test/ejabberd_admin_test.exs79
-rw-r--r--test/ejabberd_auth_mock.exs57
-rw-r--r--test/ejabberd_commands_test.exs437
-rw-r--r--test/ejabberd_hooks_test.exs6
-rw-r--r--test/ejabberd_oauth_mock.exs30
-rw-r--r--test/ejabberd_sm_mock.exs106
-rw-r--r--test/elixir_SUITE.erl28
-rw-r--r--test/mod_admin_extra_test.exs699
-rw-r--r--test/mod_http_api_test.exs188
-rw-r--r--test/mod_last_mock.exs65
-rw-r--r--test/mod_roster_mock.exs192
11 files changed, 1838 insertions, 49 deletions
diff --git a/test/ejabberd_admin_test.exs b/test/ejabberd_admin_test.exs
new file mode 100644
index 00000000..1c999314
--- /dev/null
+++ b/test/ejabberd_admin_test.exs
@@ -0,0 +1,79 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2015 ProcessOne
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# ----------------------------------------------------------------------
+
+defmodule EjabberdAdminTest do
+ use ExUnit.Case, async: false
+
+ @author "jsautret@process-one.net"
+
+ setup_all do
+ :mnesia.start
+ # For some myterious reason, :ejabberd_commands.init mays
+ # sometimes fails if module is not loaded before
+ {:module, :ejabberd_commands} = Code.ensure_loaded(:ejabberd_commands)
+ :ejabberd_commands.init
+ :ejabberd_admin.start
+ :ok
+ end
+
+ setup do
+ :ok
+ end
+
+ test "Logvel can be set and retrieved" do
+ :ejabberd_logger.start()
+
+ assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [1])
+ assert {1, :critical, 'Critical'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [2])
+ assert {2, :error, 'Error'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [3])
+ assert {3, :warning, 'Warning'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ assert {:wrong_loglevel, 6} ==
+ catch_throw :ejabberd_commands.execute_command(:set_loglevel, [6])
+ assert {3, :warning, 'Warning'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [4])
+ assert {4, :info, 'Info'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [5])
+ assert {5, :debug, 'Debug'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ assert :lager == :ejabberd_commands.execute_command(:set_loglevel, [0])
+ assert {0, :no_log, 'No log'} ==
+ :ejabberd_commands.execute_command(:get_loglevel, [])
+
+ end
+
+ test "command status works with ejabberd stopped" do
+ assert :ejabberd_not_running ==
+ elem(:ejabberd_commands.execute_command(:status, []), 0)
+ end
+
+end
diff --git a/test/ejabberd_auth_mock.exs b/test/ejabberd_auth_mock.exs
new file mode 100644
index 00000000..495c527f
--- /dev/null
+++ b/test/ejabberd_auth_mock.exs
@@ -0,0 +1,57 @@
+ # ejabberd_auth mock
+ ######################
+
+defmodule EjabberdAuthMock do
+
+ @author "jsautret@process-one.net"
+ @agent __MODULE__
+
+ def init do
+ try do
+ Agent.stop(@agent)
+ catch
+ :exit, _e -> :ok
+ end
+
+ {:ok, _pid} = Agent.start_link(fn -> %{} end, name: @agent)
+
+ mock(:ejabberd_auth, :is_user_exists,
+ fn (user, domain) ->
+ Agent.get(@agent, fn users -> Map.get(users, {user, domain}) end) != nil
+ end)
+ mock(:ejabberd_auth, :get_password_s,
+ fn (user, domain) ->
+ Agent.get(@agent, fn users -> Map.get(users, {user, domain}, "") end )
+ end)
+ mock(:ejabberd_auth, :check_password,
+ fn (user, domain, password) ->
+ Agent.get(@agent, fn users ->
+ Map.get(users, {user, domain}) end) == password
+ end)
+ mock(:ejabberd_auth, :set_password,
+ fn (user, domain, password) ->
+ Agent.update(@agent, fn users ->
+ Map.put(users, {user, domain}, password) end)
+ end)
+ end
+
+ def create_user(user, domain, password) do
+ Agent.update(@agent, fn users -> Map.put(users, {user, domain}, password) end)
+ end
+
+ ####################################################################
+ # Helpers
+ ####################################################################
+
+ # TODO refactor: Move to ejabberd_test_mock
+ def mock(module, function, fun) do
+ try do
+ :meck.new(module)
+ catch
+ :error, {:already_started, _pid} -> :ok
+ end
+
+ :meck.expect(module, function, fun)
+ end
+
+end
diff --git a/test/ejabberd_commands_test.exs b/test/ejabberd_commands_test.exs
index 0c06fc2c..b3f10000 100644
--- a/test/ejabberd_commands_test.exs
+++ b/test/ejabberd_commands_test.exs
@@ -19,39 +19,406 @@
# ----------------------------------------------------------------------
defmodule EjabberdCommandsTest do
- @author "mremond@process-one.net"
-
- use ExUnit.Case, async: true
-
- require Record
- Record.defrecord :ejabberd_commands, Record.extract(:ejabberd_commands, from_lib: "ejabberd/include/ejabberd_commands.hrl")
-
- setup_all do
- :ejabberd_commands.init
- end
-
- test "Check that we can register a command" do
- assert :ejabberd_commands.register_commands([user_test_command]) == :ok
- commands = :ejabberd_commands.list_commands
- assert Enum.member?(commands, {:test_user, [], "Test user"})
- end
-
-# test "Check that a user can use a user command" do
-# [Command] = ets:lookup(ejabberd_commands, test_user),
-# AccessCommands = ejabberd_commands:get_access_commands(undefined),
-# ejabberd_commands:check_access_commands(AccessCommands, {<<"test">>,<<"localhost">>, {oauth,<<"MyToken">>}, false}, test_user, Command, []).
-# end
-
- defp user_test_command do
- ejabberd_commands(name: :test_user, tags: [:roster],
- desc: "Test user",
- policy: :user,
- module: __MODULE__,
- function: :test_user,
- args: [],
- result: {:contacts, {:list, {:contact, {:tuple, [
- {:jid, :string},
- {:nick, :string}
- ]}}}})
- end
+ use ExUnit.Case, async: false
+
+ @author "jsautret@process-one.net"
+
+ # mocked callback module
+ @module :test_module
+ # Admin user
+ @admin "admin"
+ @adminpass "adminpass"
+ # Non admin user
+ @user "user"
+ @userpass "userpass"
+ # XMPP domain
+ @domain "domain"
+
+ require Record
+ Record.defrecord :ejabberd_commands, Record.extract(:ejabberd_commands,
+ from: "ejabberd_commands.hrl")
+
+ setup_all do
+ try do
+ :stringprep.start
+ rescue
+ _ -> :ok
+ end
+ :mnesia.start
+ EjabberdOauthMock.init
+ :ok
+ end
+
+ setup do
+ :meck.unload
+ :meck.new(@module, [:non_strict])
+ :ejabberd_commands.init
+ end
+
+ test "API command can be registered, listed and unregistered" do
+ command = ejabberd_commands name: :test, module: @module,
+ function: :test_command
+
+ assert :ok == :ejabberd_commands.register_commands [command]
+ commands = :ejabberd_commands.list_commands
+ assert Enum.member? commands, {:test, [], ''}
+
+ assert :ok == :ejabberd_commands.unregister_commands [command]
+ commands = :ejabberd_commands.list_commands
+ refute Enum.member? commands, {:test, [], ''}
+ end
+
+
+ test "API command with versions can be registered, listed and unregistered" do
+ command1 = ejabberd_commands name: :test, module: @module,
+ function: :test_command, version: 1, desc: 'version1'
+ command3 = ejabberd_commands name: :test, module: @module,
+ function: :test_command, version: 3, desc: 'version3'
+ assert :ejabberd_commands.register_commands [command1, command3]
+
+ version1 = {:test, [], 'version1'}
+ version3 = {:test, [], 'version3'}
+
+ # default version is latest one
+ commands = :ejabberd_commands.list_commands
+ refute Enum.member? commands, version1
+ assert Enum.member? commands, version3
+
+ # no such command in APIv0
+ commands = :ejabberd_commands.list_commands 0
+ refute Enum.member? commands, version1
+ refute Enum.member? commands, version3
+
+ commands = :ejabberd_commands.list_commands 1
+ assert Enum.member? commands, version1
+ refute Enum.member? commands, version3
+
+ commands = :ejabberd_commands.list_commands 2
+ assert Enum.member? commands, version1
+ refute Enum.member? commands, version3
+
+ commands = :ejabberd_commands.list_commands 3
+ refute Enum.member? commands, version1
+ assert Enum.member? commands, version3
+
+ commands = :ejabberd_commands.list_commands 4
+ refute Enum.member? commands, version1
+ assert Enum.member? commands, version3
+
+ assert :ok == :ejabberd_commands.unregister_commands [command1]
+
+ commands = :ejabberd_commands.list_commands 1
+ refute Enum.member? commands, version1
+ refute Enum.member? commands, version3
+
+ commands = :ejabberd_commands.list_commands 3
+ refute Enum.member? commands, version1
+ assert Enum.member? commands, version3
+
+ assert :ok == :ejabberd_commands.unregister_commands [command3]
+
+ commands = :ejabberd_commands.list_commands 1
+ refute Enum.member? commands, version1
+ refute Enum.member? commands, version3
+
+ commands = :ejabberd_commands.list_commands 3
+ refute Enum.member? commands, version1
+ refute Enum.member? commands, version3
+ end
+
+
+ test "API command can be registered and executed" do
+ # Create & register a mocked command test() -> :result
+ command_name = :test
+ function = :test_command
+ command = ejabberd_commands(name: command_name,
+ module: @module,
+ function: function)
+ :meck.expect @module, function, fn -> :result end
+ assert :ok == :ejabberd_commands.register_commands [command]
+
+ assert :result == :ejabberd_commands.execute_command(command_name, [])
+
+ assert :meck.validate @module
+ end
+
+ test "API command with versions can be registered and executed" do
+ command_name = :test
+
+ function1 = :test_command1
+ command1 = ejabberd_commands(name: command_name,
+ version: 1,
+ module: @module,
+ function: function1)
+ :meck.expect(@module, function1, fn -> :result1 end)
+
+ function3 = :test_command3
+ command3 = ejabberd_commands(name: command_name,
+ version: 3,
+ module: @module,
+ function: function3)
+ :meck.expect(@module, function3, fn -> :result3 end)
+
+ assert :ok == :ejabberd_commands.register_commands [command1, command3]
+
+ # default version is latest one
+ assert :result3 == :ejabberd_commands.execute_command(command_name, [])
+ # no such command in APIv0
+ assert :unknown_command ==
+ catch_throw :ejabberd_commands.execute_command(command_name, [], 0)
+ assert :result1 == :ejabberd_commands.execute_command(command_name, [], 1)
+ assert :result1 == :ejabberd_commands.execute_command(command_name, [], 2)
+ assert :result3 == :ejabberd_commands.execute_command(command_name, [], 3)
+ assert :result3 == :ejabberd_commands.execute_command(command_name, [], 4)
+
+ assert :meck.validate @module
+ end
+
+
+
+ test "API command with user policy" do
+ mock_commands_config
+
+ # Register a command test(user, domain) -> {:versionN, user, domain}
+ # with policy=user and versions 1 & 3
+ command_name = :test
+ command1 = ejabberd_commands(name: command_name,
+ module: @module,
+ function: :test_command1,
+ policy: :user, version: 1)
+ command3 = ejabberd_commands(name: command_name,
+ module: @module,
+ function: :test_command3,
+ policy: :user, version: 3)
+ :meck.expect(@module, :test_command1,
+ fn(user, domain) when is_binary(user) and is_binary(domain) ->
+ {:version1, user, domain}
+ end)
+ :meck.expect(@module, :test_command3,
+ fn(user, domain) when is_binary(user) and is_binary(domain) ->
+ {:version3, user, domain}
+ end)
+ assert :ok == :ejabberd_commands.register_commands [command1, command3]
+
+ # A normal user must not pass user info as parameter
+ assert {:version1, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ @userpass, false},
+ command_name,
+ [], 2)
+ assert {:version3, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ @userpass, false},
+ command_name,
+ [], 3)
+ token = EjabberdOauthMock.get_token @user, @domain, command_name
+ assert {:version3, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ {:oauth, token}, false},
+ command_name,
+ [], 4)
+ # Expired oauth token
+ token = EjabberdOauthMock.get_token @user, @domain, command_name, 1
+ :timer.sleep 1500
+ assert {:error, :invalid_account_data} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ {:oauth, token}, false},
+ command_name,
+ [], 4)
+ # Wrong oauth scope
+ token = EjabberdOauthMock.get_token @user, @domain, :bad_command
+ assert {:error, :invalid_account_data} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ {:oauth, token}, false},
+ command_name,
+ [], 4)
+
+
+ assert :function_clause ==
+ catch_error :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ @userpass, false},
+ command_name,
+ [@user, @domain], 2)
+ # @user is not admin
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ @userpass, true},
+ command_name,
+ [], 2)
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ @userpass, true},
+ command_name,
+ [@user, @domain], 2)
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ {:oauth, token}, true},
+ command_name,
+ [@user, @domain], 2)
+
+
+ # An admin must explicitely pass user info
+ assert {:version1, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined, :admin,
+ command_name, [@user, @domain], 2)
+ assert {:version3, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined, :admin,
+ command_name, [@user, @domain], 4)
+ assert {:version1, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain, @adminpass, true},
+ command_name, [@user, @domain], 1)
+ token = EjabberdOauthMock.get_token @admin, @domain, command_name
+ assert {:version3, @user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain, {:oauth, token}, true},
+ command_name, [@user, @domain], 3)
+ # Wrong @admin password
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ @adminpass<>"bad", true},
+ command_name,
+ [@user, @domain], 3)
+ # @admin calling as a normal user
+ assert {:version3, @admin, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ @adminpass, false},
+ command_name, [], 5)
+ assert {:version3, @admin, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ {:oauth, token}, false},
+ command_name, [], 6)
+ assert :function_clause ==
+ catch_error :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ @adminpass, false},
+ command_name,
+ [@user, @domain], 5)
+ assert :meck.validate @module
+ end
+
+
+
+ test "API command with admin policy" do
+ mock_commands_config
+
+ # Register a command test(user, domain) -> {user, domain}
+ # with policy=admin
+ command_name = :test
+ function = :test_command
+ command = ejabberd_commands(name: command_name,
+ args: [{:user, :binary}, {:host, :binary}],
+ module: @module,
+ function: function,
+ policy: :admin)
+ :meck.expect(@module, function,
+ fn(user, domain) when is_binary(user) and is_binary(domain) ->
+ {user, domain}
+ end)
+ assert :ok == :ejabberd_commands.register_commands [command]
+
+ # A normal user cannot call the command
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@user, @domain,
+ @userpass, false},
+ command_name,
+ [@user, @domain])
+
+ # An admin can call the command
+ assert {@user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ @adminpass, true},
+ command_name,
+ [@user, @domain])
+
+ # An admin can call the command with oauth token
+ token = EjabberdOauthMock.get_token @admin, @domain, command_name
+ assert {@user, @domain} ==
+ :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ {:oauth, token}, true},
+ command_name,
+ [@user, @domain])
+
+
+ # An admin with bad password cannot call the command
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ "bad"<>@adminpass, false},
+ command_name,
+ [@user, @domain])
+
+ # An admin cannot call the command with bad oauth token
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ {:oauth, "bad"<>token}, true},
+ command_name,
+ [@user, @domain])
+
+ # An admin as a normal user cannot call the command
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ @adminpass, false},
+ command_name,
+ [@user, @domain])
+
+ # An admin as a normal user cannot call the command with oauth token
+ assert {:error, :account_unprivileged} ==
+ catch_throw :ejabberd_commands.execute_command(:undefined,
+ {@admin, @domain,
+ {:oauth, token}, false},
+ command_name,
+ [@user, @domain])
+
+ assert :meck.validate @module
+ end
+
+
+ ##########################################################
+ # Utils
+
+ # Mock a config where only @admin user is allowed to call commands
+ # as admin
+ def mock_commands_config do
+ EjabberdAuthMock.init
+ EjabberdAuthMock.create_user @user, @domain, @userpass
+ EjabberdAuthMock.create_user @admin, @domain, @adminpass
+
+ :meck.new :ejabberd_config
+ :meck.expect(:ejabberd_config, :get_option,
+ fn(:commands_admin_access, _, _) -> :commands_admin_access
+ (:oauth_access, _, _) -> :all
+ (_, _, default) -> default
+ end)
+ :meck.expect(:ejabberd_config, :get_myhosts,
+ fn() -> [@domain] end)
+ :meck.new :acl
+ :meck.expect(:acl, :match_rule,
+ fn(@domain, :commands_admin_access, user) ->
+ case :jlib.make_jid(@admin, @domain, "") do
+ ^user -> :allow
+ _ -> :deny
+ end
+ (@domain, :all, _user) ->
+ :allow
+ end)
+ end
+
end
diff --git a/test/ejabberd_hooks_test.exs b/test/ejabberd_hooks_test.exs
index 6493642d..a69fbbd6 100644
--- a/test/ejabberd_hooks_test.exs
+++ b/test/ejabberd_hooks_test.exs
@@ -30,14 +30,14 @@
# log as we are exercising hook handler recovery from that situation.
defmodule EjabberdHooksTest do
- use ExUnit.Case, async: true
-
+ use ExUnit.Case, async: false
+
@author "mremond@process-one.net"
@host <<"domain.net">>
@self __MODULE__
setup_all do
- {:ok, _Pid} = :ejabberd_hooks.start_link
+ {:ok, _pid} = :ejabberd_hooks.start_link
:ok
end
diff --git a/test/ejabberd_oauth_mock.exs b/test/ejabberd_oauth_mock.exs
new file mode 100644
index 00000000..2c1b8cf9
--- /dev/null
+++ b/test/ejabberd_oauth_mock.exs
@@ -0,0 +1,30 @@
+ # ejabberd_oauth mock
+ ######################
+
+defmodule EjabberdOauthMock do
+
+ @author "jsautret@process-one.net"
+
+ def init() do
+ :mnesia.start
+ :mnesia.create_table(:oauth_token,
+ [ram_copies: [node],
+ attributes: [:oauth_token, :us, :scope, :expire]])
+ end
+
+ def get_token(user, domain, command, expiration \\ 3600) do
+ now = {megasecs, secs, _} = :os.timestamp
+ expire = 1000000 * megasecs + secs + expiration
+ :random.seed now
+ token = to_string :random.uniform(100000000)
+
+ {:ok, _} = :ejabberd_oauth.associate_access_token(token,
+ [{"resource_owner",
+ {:user, user, domain}},
+ {"scope", [to_string command]},
+ {"expiry_time", expire}],
+ :undefined)
+ token
+ end
+
+end
diff --git a/test/ejabberd_sm_mock.exs b/test/ejabberd_sm_mock.exs
new file mode 100644
index 00000000..0c2fc163
--- /dev/null
+++ b/test/ejabberd_sm_mock.exs
@@ -0,0 +1,106 @@
+ # ejabberd_sm mock
+ ######################
+
+defmodule EjabberdSmMock do
+ @author "jsautret@process-one.net"
+
+ require Record
+ Record.defrecord :session, Record.extract(:session,
+ from: "ejabberd_sm.hrl")
+ Record.defrecord :jid, Record.extract(:jid,
+ from: "jlib.hrl")
+
+ @agent __MODULE__
+
+ def init do
+ ModLastMock.init
+
+ try do
+ Agent.stop(@agent)
+ catch
+ :exit, _e -> :ok
+ end
+
+ {:ok, _pid} = Agent.start_link(fn -> [] end, name: @agent)
+
+ mock(:ejabberd_sm, :get_user_resources,
+ fn (user, domain) -> for s <- get_sessions(user, domain), do: s.resource end)
+
+ mock(:ejabberd_sm, :route,
+ fn (_from, to, {:broadcast, {:exit, _reason}}) ->
+ user = jid(to, :user)
+ domain = jid(to, :server)
+ resource = jid(to, :resource)
+ disconnect_resource(user, domain, resource)
+ :ok
+ (_, _, _) -> :ok
+ end)
+
+ end
+
+ def connect_resource(user, domain, resource,
+ opts \\ [priority: 1, conn: :c2s]) do
+ Agent.update(@agent, fn sessions ->
+ session = %{user: user, domain: domain, resource: resource,
+ timestamp: :os.timestamp, pid: self, node: node,
+ auth_module: :ejabberd_auth, ip: :undefined,
+ priority: opts[:priority], conn: opts[:conn]}
+ [session | sessions]
+ end)
+ end
+
+ def disconnect_resource(user, domain, resource) do
+ disconnect_resource(user, domain, resource, ModLastMock.now)
+ end
+
+ def disconnect_resource(user, domain, resource, timestamp) do
+ Agent.update(@agent, fn sessions ->
+ for s <- sessions,
+ s.user != user or s.domain != domain or s.resource != resource, do: s
+ end)
+ ModLastMock.set_last user, domain, "", timestamp
+ end
+
+ def get_sessions() do
+ Agent.get(@agent, fn sessions -> sessions end)
+ end
+
+ def get_sessions(user, domain) do
+ Agent.get(@agent, fn sessions ->
+ for s <- sessions, s.user == user, s.domain == domain, do: s
+ end)
+ end
+
+ def get_session(user, domain, resource) do
+ Agent.get(@agent, fn sessions ->
+ for s <- sessions,
+ s.user == user, s.domain == domain, s.resource == resource, do: s
+ end)
+ end
+
+ def to_record(s) do
+ session(usr: {s.user, s.domain, s.ressource},
+ us: {s.user, s.domain},
+ sid: {s.timestamp, s.pid},
+ priority: s.priority,
+ info: [conn: s.conn, ip: s.ip, node: s.node,
+ oor: false, auth_module: s.auth_module])
+ end
+
+ ####################################################################
+ # Helpers
+ ####################################################################
+
+
+ # TODO refactor: Move to ejabberd_test_mock
+ def mock(module, function, fun) do
+ try do
+ :meck.new(module)
+ catch
+ :error, {:already_started, _pid} -> :ok
+ end
+
+ :meck.expect(module, function, fun)
+ end
+
+end
diff --git a/test/elixir_SUITE.erl b/test/elixir_SUITE.erl
index b9a0b1a2..f2c64773 100644
--- a/test/elixir_SUITE.erl
+++ b/test/elixir_SUITE.erl
@@ -19,6 +19,7 @@
init_per_suite(Config) ->
check_meck(),
+ code:add_pathz(filename:join(test_dir(), "../include")),
Config.
init_per_testcase(_TestCase, Config) ->
@@ -27,13 +28,13 @@ init_per_testcase(_TestCase, Config) ->
all() ->
case is_elixir_available() of
- true ->
- Dir = test_dir(),
- filelib:fold_files(Dir, ".*\.exs", false,
- fun(Filename, Acc) -> [list_to_atom(filename:basename(Filename)) | Acc] end,
- []);
- false ->
- []
+ true ->
+ Dir = test_dir(),
+ filelib:fold_files(Dir, ".*\.exs", false,
+ fun(Filename, Acc) -> [list_to_atom(filename:basename(Filename)) | Acc] end,
+ []);
+ false ->
+ []
end.
check_meck() ->
@@ -56,16 +57,21 @@ is_elixir_available() ->
undefined_function(?MODULE, Func, Args) ->
case lists:suffix(".exs", atom_to_list(Func)) of
- true ->
- run_elixir_test(Func);
- false ->
- error_handler:undefined_function(?MODULE, Func, Args)
+ true ->
+ run_elixir_test(Func);
+ false ->
+ error_handler:undefined_function(?MODULE, Func, Args)
end;
undefined_function(Module, Func, Args) ->
error_handler:undefined_function(Module, Func,Args).
run_elixir_test(Func) ->
'Elixir.ExUnit':start([]),
+ filelib:fold_files(test_dir(), ".*\\.exs\$", true,
+ fun (File, N) ->
+ 'Elixir.Code':require_file(list_to_binary(File)),
+ N+1
+ end, 0),
'Elixir.Code':load_file(list_to_binary(filename:join(test_dir(), atom_to_list(Func)))),
%% I did not use map syntax, so that this file can still be build under R16
ResultMap = 'Elixir.ExUnit':run(),
diff --git a/test/mod_admin_extra_test.exs b/test/mod_admin_extra_test.exs
new file mode 100644
index 00000000..7fa39eef
--- /dev/null
+++ b/test/mod_admin_extra_test.exs
@@ -0,0 +1,699 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2015 ProcessOne
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# ----------------------------------------------------------------------
+
+defmodule EjabberdModAdminExtraTest do
+ use ExUnit.Case, async: false
+
+ @author "jsautret@process-one.net"
+
+ @user "user"
+ @domain "domain"
+ @password "password"
+ @resource "resource"
+
+ require Record
+ Record.defrecord :jid, Record.extract(:jid,
+ from: "jlib.hrl")
+
+ setup_all do
+ try do
+ :stringprep.start
+ :mnesia.start
+ :p1_sha.load_nif
+ rescue
+ _ -> :ok
+ end
+ :ejabberd_commands.init
+ :mod_admin_extra.start(@domain, [])
+ :sel_application.start_app(:moka)
+ {:ok, _pid} = :ejabberd_hooks.start_link
+ :ok
+ end
+
+ setup do
+ :meck.unload
+ EjabberdAuthMock.init
+ EjabberdSmMock.init
+ ModRosterMock.init(@domain, :mod_admin_extra)
+ :ok
+ end
+
+ ###################### Accounts
+ test "check_account works" do
+ EjabberdAuthMock.create_user @user, @domain, @password
+
+ assert :ejabberd_commands.execute_command(:check_account, [@user, @domain])
+ refute :ejabberd_commands.execute_command(:check_account, [@user, "bad_domain"])
+ refute :ejabberd_commands.execute_command(:check_account, ["bad_user", @domain])
+
+ assert :meck.validate :ejabberd_auth
+ end
+
+ test "check_password works" do
+
+ EjabberdAuthMock.create_user @user, @domain, @password
+
+ assert :ejabberd_commands.execute_command(:check_password,
+ [@user, @domain, @password])
+ refute :ejabberd_commands.execute_command(:check_password,
+ [@user, @domain, "bad_password"])
+ refute :ejabberd_commands.execute_command(:check_password,
+ [@user, "bad_domain", @password])
+ refute :ejabberd_commands.execute_command(:check_password,
+ ["bad_user", @domain, @password])
+
+ assert :meck.validate :ejabberd_auth
+
+ end
+
+ test "check_password_hash works" do
+
+ EjabberdAuthMock.create_user @user, @domain, @password
+ hash = "5F4DCC3B5AA765D61D8327DEB882CF99" # echo -n password|md5
+
+ assert :ejabberd_commands.execute_command(:check_password_hash,
+ [@user, @domain, hash, "md5"])
+ refute :ejabberd_commands.execute_command(:check_password_hash,
+ [@user, @domain, "bad_hash", "md5"])
+ refute :ejabberd_commands.execute_command(:check_password_hash,
+ [@user, "bad_domain", hash, "md5"])
+ refute :ejabberd_commands.execute_command(:check_password_hash,
+ ["bad_user", @domain, hash, "md5"])
+
+ hash = "5BAA61E4C9B93F3F0682250B6CF8331B7EE68FD8" # echo -n password|shasum
+ assert :ejabberd_commands.execute_command(:check_password_hash,
+ [@user, @domain, hash, "sha"])
+
+ assert :unkown_hash_method ==
+ catch_throw :ejabberd_commands.execute_command(:check_password_hash,
+ [@user, @domain, hash, "bad_method"])
+
+ assert :meck.validate :ejabberd_auth
+
+ end
+
+ test "change_password works" do
+ EjabberdAuthMock.create_user @user, @domain, @password
+
+ assert :ejabberd_commands.execute_command(:change_password,
+ [@user, @domain, "new_password"])
+ refute :ejabberd_commands.execute_command(:check_password,
+ [@user, @domain, @password])
+ assert :ejabberd_commands.execute_command(:check_password,
+ [@user, @domain, "new_password"])
+ assert {:not_found, 'unknown_user'} ==
+ catch_throw :ejabberd_commands.execute_command(:change_password,
+ ["bad_user", @domain,
+ @password])
+ assert :meck.validate :ejabberd_auth
+ end
+
+ test "check_users_registration works" do
+ EjabberdAuthMock.create_user @user<>"1", @domain, @password
+ EjabberdAuthMock.create_user @user<>"2", @domain, @password
+ EjabberdAuthMock.create_user @user<>"3", @domain, @password
+
+ assert [{@user<>"0", @domain, 0},
+ {@user<>"1", @domain, 1},
+ {@user<>"2", @domain, 1},
+ {@user<>"3", @domain, 1}] ==
+ :ejabberd_commands.execute_command(:check_users_registration,
+ [[{@user<>"0", @domain},
+ {@user<>"1", @domain},
+ {@user<>"2", @domain},
+ {@user<>"3", @domain}]])
+
+ assert :meck.validate :ejabberd_auth
+
+ end
+
+ ###################### Sessions
+
+ test "num_resources works" do
+ assert 0 == :ejabberd_commands.execute_command(:num_resources,
+ [@user, @domain])
+
+ EjabberdSmMock.connect_resource @user, @domain, @resource
+ assert 1 == :ejabberd_commands.execute_command(:num_resources,
+ [@user, @domain])
+
+ EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
+ assert 2 == :ejabberd_commands.execute_command(:num_resources,
+ [@user, @domain])
+
+ EjabberdSmMock.connect_resource @user<>"1", @domain, @resource
+ assert 2 == :ejabberd_commands.execute_command(:num_resources,
+ [@user, @domain])
+
+ EjabberdSmMock.disconnect_resource @user, @domain, @resource
+ assert 1 == :ejabberd_commands.execute_command(:num_resources,
+ [@user, @domain])
+
+ assert :meck.validate :ejabberd_sm
+ end
+
+ test "resource_num works" do
+ EjabberdSmMock.connect_resource @user, @domain, @resource<>"3"
+ EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
+ EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
+
+ assert :bad_argument ==
+ elem(catch_throw(:ejabberd_commands.execute_command(:resource_num,
+ [@user, @domain, 0])), 0)
+ assert @resource<>"1" ==
+ :ejabberd_commands.execute_command(:resource_num, [@user, @domain, 1])
+ assert @resource<>"3" ==
+ :ejabberd_commands.execute_command(:resource_num, [@user, @domain, 3])
+ assert :bad_argument ==
+ elem(catch_throw(:ejabberd_commands.execute_command(:resource_num,
+ [@user, @domain, 4])), 0)
+ assert :meck.validate :ejabberd_sm
+ end
+
+ test "kick_session works" do
+ EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
+ EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
+ EjabberdSmMock.connect_resource @user, @domain, @resource<>"3"
+
+ assert 3 == length EjabberdSmMock.get_sessions @user, @domain
+ assert 1 == length EjabberdSmMock.get_session @user, @domain, @resource<>"2"
+
+ assert :ok ==
+ :ejabberd_commands.execute_command(:kick_session,
+ [@user, @domain,
+ @resource<>"2", "kick"])
+
+ assert 2 == length EjabberdSmMock.get_sessions @user, @domain
+ assert 0 == length EjabberdSmMock.get_session @user, @domain, @resource<>"2"
+
+ assert :meck.validate :ejabberd_sm
+ end
+
+ ###################### Last
+
+ test "get_last works" do
+
+ assert 'Never' ==
+ :ejabberd_commands.execute_command(:get_last, [@user, @domain])
+
+ EjabberdSmMock.connect_resource @user, @domain, @resource<>"1"
+ EjabberdSmMock.connect_resource @user, @domain, @resource<>"2"
+
+ assert 'Online' ==
+ :ejabberd_commands.execute_command(:get_last, [@user, @domain])
+
+ EjabberdSmMock.disconnect_resource @user, @domain, @resource<>"1"
+
+ assert 'Online' ==
+ :ejabberd_commands.execute_command(:get_last, [@user, @domain])
+
+ now = {megasecs, secs, _microsecs} = :os.timestamp
+ timestamp = megasecs * 1000000 + secs
+ EjabberdSmMock.disconnect_resource(@user, @domain, @resource<>"2",
+ timestamp)
+ {{year, month, day}, {hour, minute, second}} = :calendar.now_to_local_time now
+ result = List.flatten(:io_lib.format(
+ "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w ",
+ [year, month, day, hour, minute, second]))
+ assert result ==
+ :ejabberd_commands.execute_command(:get_last, [@user, @domain])
+
+ assert :meck.validate :mod_last
+ end
+
+ ###################### Roster
+
+ test "add_rosteritem and delete_rosteritem work" do
+ # Connect user
+ # Add user1 & user2 to user's roster
+ # Remove user1 & user2 from user's roster
+
+ EjabberdSmMock.connect_resource @user, @domain, @resource
+
+ assert [] == ModRosterMock.get_roster(@user, @domain)
+
+ assert :ok ==
+ :ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
+ @user<>"1", @domain,
+ "nick1",
+ "group1",
+ "both"])
+ # Check that user1 is the only item of the user's roster
+ result = ModRosterMock.get_roster(@user, @domain)
+ assert 1 == length result
+ [{{@user, @domain, jid}, opts}] = result
+ assert @user<>"1@"<>@domain == jid
+ assert "nick1" == opts.nick
+ assert ["group1"] == opts.groups
+ assert :both == opts.subs
+
+ # Check that the item roster user1 was pushed with subscription
+ # 'both' to user online ressource
+ jid = :jlib.make_jid(@user, @domain, @resource)
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"1", @domain, ""}, :both}}])
+
+ assert :ok ==
+ :ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
+ @user<>"2", @domain,
+ "nick2",
+ "group2",
+ "both"])
+ result = ModRosterMock.get_roster(@user, @domain)
+ assert 2 == length result
+
+
+ # Check that the item roster user2 was pushed with subscription
+ # 'both' to user online ressource
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"2", @domain, ""}, :both}}])
+
+
+ :ejabberd_commands.execute_command(:delete_rosteritem, [@user, @domain,
+ @user<>"1", @domain])
+ result = ModRosterMock.get_roster(@user, @domain)
+ assert 1 == length result
+ [{{@user, @domain, jid}, opts}] = result
+ assert @user<>"2@"<>@domain == jid
+ assert "nick2" == opts.nick
+ assert ["group2"] == opts.groups
+ assert :both == opts.subs
+
+ # Check that the item roster user1 was pushed with subscription
+ # 'none' to user online ressource
+ jid = :jlib.make_jid(@user, @domain, @resource)
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"1", @domain, ""}, :none}}])
+
+ :ejabberd_commands.execute_command(:delete_rosteritem, [@user, @domain,
+ @user<>"2", @domain])
+
+ # Check that the item roster user2 was pushed with subscription
+ # 'none' to user online ressource
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"2", @domain, ""}, :none}}])
+
+ # Check that nothing else was pushed to user resource
+ jid = jid(user: @user, server: @domain, resource: :_,
+ luser: @user, lserver: @domain, lresource: :_)
+ assert 4 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, :_, :_}}])
+
+ assert [] == ModRosterMock.get_roster(@user, @domain)
+ assert :meck.validate :ejabberd_sm
+
+ end
+
+ test "get_roster works" do
+ assert [] == ModRosterMock.get_roster(@user, @domain)
+ assert [] == :ejabberd_commands.execute_command(:get_roster, [@user, @domain],
+ :admin)
+
+ assert :ok ==
+ :ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
+ @user<>"1", @domain,
+ "nick1",
+ "group1",
+ "both"])
+ assert [{@user<>"1@"<>@domain, "", 'both', 'none', "group1"}] ==
+ :ejabberd_commands.execute_command(:get_roster, [@user, @domain], :admin)
+ assert :ok ==
+ :ejabberd_commands.execute_command(:add_rosteritem, [@user, @domain,
+ @user<>"2", @domain,
+ "nick2",
+ "group2",
+ "none"])
+ result = :ejabberd_commands.execute_command(:get_roster, [@user, @domain], :admin)
+ assert 2 == length result
+ assert Enum.member?(result, {@user<>"1@"<>@domain, "", 'both', 'none', "group1"})
+ assert Enum.member?(result, {@user<>"2@"<>@domain, "", 'none', 'none', "group2"})
+
+ end
+
+
+ test "link_contacts & unlink_contacts work" do
+ # Create user1 and keep it offline
+ EjabberdAuthMock.create_user @user<>"1", @domain, @password
+
+ # fail if one of the users doesn't exist locally
+ assert 404 ==
+ :ejabberd_commands.execute_command(:link_contacts, [@user<>"1@"<>@domain,
+ "nick1",
+ "group1",
+ @user<>"2@"<>@domain,
+ "nick2",
+ "group2"])
+
+ # Create user2 and connect 2 resources
+ EjabberdAuthMock.create_user @user<>"2", @domain, @password
+
+ EjabberdSmMock.connect_resource @user<>"2", @domain, @resource<>"1"
+ EjabberdSmMock.connect_resource @user<>"2", @domain, @resource<>"2"
+
+ # Link both user1 & user2 (returns 0 if OK)
+ assert 0 ==
+ :ejabberd_commands.execute_command(:link_contacts, [@user<>"1@"<>@domain,
+ "nick1",
+ "group2",
+ @user<>"2@"<>@domain,
+ "nick2",
+ "group1"])
+ assert [{@user<>"2@"<>@domain, "", 'both', 'none', "group2"}] ==
+ :ejabberd_commands.execute_command(:get_roster, [@user<>"1", @domain], :admin)
+
+ assert [{@user<>"1@"<>@domain, "", 'both', 'none', "group1"}] ==
+ :ejabberd_commands.execute_command(:get_roster, [@user<>"2", @domain], :admin)
+
+ # Check that the item roster user1 was pushed with subscription
+ # 'both' to the 2 user2 online ressources
+ jid = :jlib.make_jid(@user<>"2", @domain, @resource<>"1")
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"1", @domain, ""}, :both}}])
+ jid = :jlib.make_jid(@user<>"2", @domain, @resource<>"2")
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"1", @domain, ""}, :both}}])
+
+
+ # Ulink both user1 & user2 (returns 0 if OK)
+ assert 0 ==
+ :ejabberd_commands.execute_command(:unlink_contacts, [@user<>"1@"<>@domain,
+ @user<>"2@"<>@domain])
+ assert [] ==
+ :ejabberd_commands.execute_command(:get_roster, [@user<>"1", @domain], :admin)
+
+ assert [] ==
+ :ejabberd_commands.execute_command(:get_roster, [@user<>"2", @domain], :admin)
+
+ # Check that the item roster user1 was pushed with subscription
+ # 'none' to the 2 user2 online ressources
+ jid = :jlib.make_jid(@user<>"2", @domain, @resource<>"1")
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"1", @domain, ""}, :none}}])
+ jid = :jlib.make_jid(@user<>"2", @domain, @resource<>"2")
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"1", @domain, ""}, :none}}])
+
+ # Check that nothing else was pushed to user2 resources
+ jid = jid(user: @user<>"2", server: @domain, resource: :_,
+ luser: @user<>"2", lserver: @domain, lresource: :_)
+ assert 4 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, :_, :_}}])
+
+ # Check nothing was pushed to user1
+ jid = jid(user: @user<>"1", server: @domain, resource: :_,
+ luser: @user<>"1", lserver: @domain, lresource: :_)
+ refute :meck.called(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, :_, :_}}])
+
+ assert :meck.validate :ejabberd_sm
+ assert :meck.validate :ejabberd_auth
+
+ end
+
+
+
+
+ test "add_contacts and delete_contacts work" do
+ # Create user, user1 & user2
+ # Connect user & user1
+ # Add user0, user1 & user2 to user's roster
+ # Remove user0, user1 & user2 from user's roster
+
+ # user doesn't exists yet, command must fail
+ assert 404 ==
+ :ejabberd_commands.execute_command(:add_contacts, [@user, @domain,
+ [{@user<>"1"<>@domain,
+ "group1",
+ "nick1"},
+ {@user<>"2"<>@domain,
+ "group2",
+ "nick2"}]
+ ])
+
+ EjabberdAuthMock.create_user @user, @domain, @password
+ EjabberdSmMock.connect_resource @user, @domain, @resource
+ EjabberdAuthMock.create_user @user<>"1", @domain, @password
+ EjabberdSmMock.connect_resource @user<>"1", @domain, @resource
+ EjabberdAuthMock.create_user @user<>"2", @domain, @password
+
+ # Add user1 & user2 in user's roster. Try also to add user0 that
+ # doesn't exists. Command is supposed to return number of added items.
+ assert 2 ==
+ :ejabberd_commands.execute_command(:add_contacts, [@user, @domain,
+ [{@user<>"0@"<>@domain,
+ "group0",
+ "nick0"},
+ {@user<>"1@"<>@domain,
+ "group1",
+ "nick1"},
+ {@user<>"2@"<>@domain,
+ "group2",
+ "nick2"}]
+ ])
+ # Check that user1 & user2 are the only items in user's roster
+ result = ModRosterMock.get_roster(@user, @domain)
+ assert 2 == length result
+ opts1 = %{nick: "nick1", groups: ["group1"], subs: :both,
+ ask: :none, askmessage: ""}
+ assert Enum.member?(result, {{@user, @domain, @user<>"1@"<>@domain}, opts1})
+ opts2 = %{nick: "nick2", groups: ["group2"], subs: :both,
+ ask: :none, askmessage: ""}
+ assert Enum.member?(result, {{@user, @domain, @user<>"2@"<>@domain}, opts2})
+
+ # Check that user is the only item in user1's roster
+ assert [{{@user<>"1", @domain, @user<>"@"<>@domain}, %{opts1|:nick => ""}}] ==
+ ModRosterMock.get_roster(@user<>"1", @domain)
+
+ # Check that user is the only item in user2's roster
+ assert [{{@user<>"2", @domain, @user<>"@"<>@domain}, %{opts2|:nick => ""}}] ==
+ ModRosterMock.get_roster(@user<>"2", @domain)
+
+
+ # Check that the roster items user1 & user2 were pushed with subscription
+ # 'both' to the user online ressource
+ jid = :jlib.make_jid(@user, @domain, @resource)
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"1", @domain, ""}, :both}}])
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"2", @domain, ""}, :both}}])
+
+ # Check that the roster item user was pushed with subscription
+ # 'both' to the user1 online ressource
+ jid = :jlib.make_jid(@user<>"1", @domain, @resource)
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user, @domain, ""}, :both}}])
+
+ # Check that nothing else was pushed to online resources
+ assert 3 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [:_, :_,
+ {:broadcast, {:item, :_, :_}}])
+
+ # Remove user1 & user2 from user's roster. Try also to remove
+ # user0 that doesn't exists. Command is supposed to return number
+ # of removed items.
+ assert 2 ==
+ :ejabberd_commands.execute_command(:remove_contacts, [@user, @domain,
+ [@user<>"0@"<>@domain,
+ @user<>"1@"<>@domain,
+ @user<>"2@"<>@domain]
+ ])
+ # Check that roster of user, user1 & user2 are empty
+ assert [] == ModRosterMock.get_roster(@user, @domain)
+ assert [] == ModRosterMock.get_roster(@user<>"1", @domain)
+ assert [] == ModRosterMock.get_roster(@user<>"2", @domain)
+
+ # Check that the roster items user1 & user2 were pushed with subscription
+ # 'none' to the user online ressource
+ jid = :jlib.make_jid(@user, @domain, @resource)
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"1", @domain, ""}, :none}}])
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"2", @domain, ""}, :none}}])
+
+ # Check that the roster item user was pushed with subscription
+ # 'none' to the user1 online ressource
+ jid = :jlib.make_jid(@user<>"1", @domain, @resource)
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user, @domain, ""}, :none}}])
+
+ # Check that nothing else was pushed to online resources
+ assert 6 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [:_, :_,
+ {:broadcast, {:item, :_, :_}}])
+
+ assert :meck.validate :ejabberd_sm
+ assert :meck.validate :ejabberd_auth
+ end
+
+
+ test "update_roster works" do
+ # user doesn't exists yet, command must fail
+ result =
+ :ejabberd_commands.execute_command(:update_roster,
+ [@user, @domain,
+ [{@user<>"1"<>@domain,
+ "group1",
+ "nick1"},
+ {@user<>"2"<>@domain,
+ "group2",
+ "nick2"}],
+ []
+ ])
+ assert :invalid_user == elem(result, 0)
+
+ EjabberdAuthMock.create_user @user, @domain, @password
+ EjabberdSmMock.connect_resource @user, @domain, @resource
+ EjabberdAuthMock.create_user @user<>"1", @domain, @password
+ EjabberdSmMock.connect_resource @user<>"1", @domain, @resource
+ EjabberdAuthMock.create_user @user<>"2", @domain, @password
+ EjabberdAuthMock.create_user @user<>"3", @domain, @password
+
+ assert :ok ==
+ :ejabberd_commands.execute_command(:update_roster,
+ [@user, @domain,
+ [{[{"username", @user<>"1"},
+ {"nick", "nick1"}]},
+ {[{"username", @user<>"2"},
+ {"nick", "nick2"},
+ {"subscription", "from"}]}],
+ []])
+
+
+ # Check that user1 & user2 are the only items in user's roster
+ result = ModRosterMock.get_roster(@user, @domain)
+
+ assert 2 == length result
+ opts1 = %{nick: "nick1", groups: [""], subs: :both,
+ ask: :none, askmessage: ""}
+ assert Enum.member?(result, {{@user, @domain, @user<>"1@"<>@domain}, opts1})
+ opts2 = %{nick: "nick2", groups: [""], subs: :from,
+ ask: :none, askmessage: ""}
+ assert Enum.member?(result, {{@user, @domain, @user<>"2@"<>@domain}, opts2})
+
+ # Check that the roster items user1 & user2 were pushed with subscription
+ # 'both' to the user online ressource
+ jid = :jlib.make_jid(@user, @domain, @resource)
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"1", @domain, ""}, :both}}])
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"2", @domain, ""}, :from}}])
+
+ # Check that nothing else was pushed to online resources
+ assert 2 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [:_, :_,
+ {:broadcast, {:item, :_, :_}}])
+
+ # Add user3 & remove user1
+ assert :ok ==
+ :ejabberd_commands.execute_command(:update_roster,
+ [@user, @domain,
+ [{[{"username", @user<>"3"},
+ {"nick", "nick3"},
+ {"subscription", "to"}]}],
+ [{[{"username", @user<>"1"}]}]
+ ])
+
+ # Check that user2 & user3 are the only items in user's roster
+ result = ModRosterMock.get_roster(@user, @domain)
+ assert 2 == length result
+ opts2 = %{nick: "nick2", groups: [""], subs: :from,
+ ask: :none, askmessage: ""}
+ assert Enum.member?(result, {{@user, @domain, @user<>"2@"<>@domain}, opts2})
+ opts1 = %{nick: "nick3", groups: [""], subs: :to,
+ ask: :none, askmessage: ""}
+ assert Enum.member?(result, {{@user, @domain, @user<>"3@"<>@domain}, opts1})
+
+ # Check that the roster items user1 & user3 were pushed with subscription
+ # 'none' & 'to' to the user online ressource
+ jid = :jlib.make_jid(@user, @domain, @resource)
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"1", @domain, ""}, :none}}])
+ assert 1 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [jid, jid,
+ {:broadcast, {:item, {@user<>"3", @domain, ""}, :to}}])
+
+ # Check that nothing else was pushed to online resources
+ assert 4 ==
+ :meck.num_calls(:ejabberd_sm, :route,
+ [:_, :_,
+ {:broadcast, {:item, :_, :_}}])
+
+ assert :meck.validate :ejabberd_sm
+ assert :meck.validate :ejabberd_auth
+ end
+
+
+# kick_user command is defined in ejabberd_sm, move to extra?
+# test "kick_user works" do
+# assert 0 == :ejabberd_commands.execute_command(:num_resources,
+# [@user, @domain])
+# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"1")
+# EjabberdSmMock.connect_resource(@user, @domain, @resource<>"2")
+# assert 2 ==
+# :ejabberd_commands.execute_command(:kick_user, [@user, @domain])
+# assert 0 == :ejabberd_commands.execute_command(:num_resources,
+# [@user, @domain])
+# assert :meck.validate :ejabberd_sm
+# end
+
+end
diff --git a/test/mod_http_api_test.exs b/test/mod_http_api_test.exs
new file mode 100644
index 00000000..ae62f28f
--- /dev/null
+++ b/test/mod_http_api_test.exs
@@ -0,0 +1,188 @@
+# ----------------------------------------------------------------------
+#
+# ejabberd, Copyright (C) 2002-2015 ProcessOne
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; either version 2 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# ----------------------------------------------------------------------
+
+defmodule ModHttpApiTest do
+ use ExUnit.Case, async: false
+
+ @author "jsautret@process-one.net"
+
+ # Admin user
+ @admin "admin"
+ @adminpass "adminpass"
+ # Non admin user
+ @user "user"
+ @userpass "userpass"
+ # XMPP domain
+ @domain "domain"
+ # mocked command
+ @command "command_test"
+ @acommand String.to_atom(@command)
+ # default API version
+ @version 0
+
+ require Record
+ Record.defrecord :request, Record.extract(:request,
+ from: "ejabberd_http.hrl")
+
+ setup_all do
+ try do
+ :stringprep.start
+ rescue
+ _ -> :ok
+ end
+ :mod_http_api.start(@domain, [])
+ EjabberdOauthMock.init
+ :ok
+ end
+
+ setup do
+ :meck.unload
+ :meck.new :ejabberd_commands
+ EjabberdAuthMock.init
+ :ok
+ end
+
+ test "HTTP GET simple command call with Basic Auth" do
+ EjabberdAuthMock.create_user @user, @domain, @userpass
+
+ # Mock a simple command() -> :ok
+ :meck.expect(:ejabberd_commands, :get_command_format,
+ fn (@acommand, {@user, @domain, @userpass, false}, @version) ->
+ {[], {:res, :rescode}}
+ end)
+ :meck.expect(:ejabberd_commands, :execute_command,
+ fn (:undefined, {@user, @domain, @userpass, false}, @acommand, [], @version) ->
+ :ok
+ end)
+
+ #:ejabberd_logger.start
+ #:ejabberd_logger.set 5
+
+ # Correct Basic Auth call
+ req = request(method: :GET,
+ path: ["api", @command],
+ q: [nokey: ""],
+ # Basic auth
+ auth: {@user<>"@"<>@domain, @userpass},
+ ip: {{127,0,0,1},60000},
+ host: @domain)
+ result = :mod_http_api.process([@command], req)
+ assert 200 == elem(result, 0) # HTTP code
+ assert "0" == elem(result, 2) # command result
+
+ # Bad password
+ req = request(method: :GET,
+ path: ["api", @command],
+ q: [nokey: ""],
+ # Basic auth
+ auth: {@user<>"@"<>@domain, @userpass<>"bad"},
+ ip: {{127,0,0,1},60000},
+ host: @domain)
+ result = :mod_http_api.process([@command], req)
+ assert 401 == elem(result, 0) # HTTP code
+
+ # Check that the command was executed only once
+ assert 1 ==
+ :meck.num_calls(:ejabberd_commands, :execute_command, :_)
+
+ assert :meck.validate :ejabberd_auth
+ assert :meck.validate :ejabberd_commands
+ #assert :ok = :meck.history(:ejabberd_commands)
+ end
+
+
+ test "HTTP GET simple command call with OAuth" do
+ EjabberdAuthMock.create_user @user, @domain, @userpass
+
+ # Mock a simple command() -> :ok
+ :meck.expect(:ejabberd_commands, :get_command_format,
+ fn (@acommand, {@user, @domain, {:oauth, _token}, false}, @version) ->
+ {[], {:res, :rescode}}
+ end)
+ :meck.expect(:ejabberd_commands, :execute_command,
+ fn (:undefined, {@user, @domain, {:oauth, _token}, false},
+ @acommand, [], @version) ->
+ :ok
+ end)
+
+ #:ejabberd_logger.start
+ #:ejabberd_logger.set 5
+
+ # Correct OAuth call
+ token = EjabberdOauthMock.get_token @user, @domain, @command
+ req = request(method: :GET,
+ path: ["api", @command],
+ q: [nokey: ""],
+ # OAuth
+ auth: {:oauth, token, []},
+ ip: {{127,0,0,1},60000},
+ host: @domain)
+ result = :mod_http_api.process([@command], req)
+ assert 200 == elem(result, 0) # HTTP code
+ assert "0" == elem(result, 2) # command result
+
+ # Wrong OAuth token
+ req = request(method: :GET,
+ path: ["api", @command],
+ q: [nokey: ""],
+ # OAuth
+ auth: {:oauth, "bad"<>token, []},
+ ip: {{127,0,0,1},60000},
+ host: @domain)
+ result = :mod_http_api.process([@command], req)
+ assert 401 == elem(result, 0) # HTTP code
+
+ # Expired OAuth token
+ token = EjabberdOauthMock.get_token @user, @domain, @command, 1
+ :timer.sleep 1500
+ req = request(method: :GET,
+ path: ["api", @command],
+ q: [nokey: ""],
+ # OAuth
+ auth: {:oauth, token, []},
+ ip: {{127,0,0,1},60000},
+ host: @domain)
+ result = :mod_http_api.process([@command], req)
+ assert 401 == elem(result, 0) # HTTP code
+
+ # Wrong OAuth scope
+ token = EjabberdOauthMock.get_token @user, @domain, "bad_command"
+ :timer.sleep 1500
+ req = request(method: :GET,
+ path: ["api", @command],
+ q: [nokey: ""],
+ # OAuth
+ auth: {:oauth, token, []},
+ ip: {{127,0,0,1},60000},
+ host: @domain)
+ result = :mod_http_api.process([@command], req)
+ assert 401 == elem(result, 0) # HTTP code
+
+ # Check that the command was executed only once
+ assert 1 ==
+ :meck.num_calls(:ejabberd_commands, :execute_command, :_)
+
+ assert :meck.validate :ejabberd_auth
+ assert :meck.validate :ejabberd_commands
+ #assert :ok = :meck.history(:ejabberd_commands)
+ end
+
+
+end
diff --git a/test/mod_last_mock.exs b/test/mod_last_mock.exs
new file mode 100644
index 00000000..7e3dc5a1
--- /dev/null
+++ b/test/mod_last_mock.exs
@@ -0,0 +1,65 @@
+ # mod_last mock
+ ######################
+
+
+defmodule ModLastMock do
+
+ require Record
+ Record.defrecord :session, Record.extract(:session,
+ from: "ejabberd_sm.hrl")
+ Record.defrecord :jid, Record.extract(:jid,
+ from: "jlib.hrl")
+
+ @author "jsautret@process-one.net"
+ @agent __MODULE__
+
+ def init do
+ try do
+ Agent.stop(@agent)
+ catch
+ :exit, _e -> :ok
+ end
+
+ {:ok, _pid} = Agent.start_link(fn -> %{} end, name: @agent)
+
+ mock(:mod_last, :get_last_info,
+ fn (user, domain) ->
+ Agent.get(@agent, fn last ->
+ case Map.get(last, {user, domain}, :not_found) do
+ {ts, status} -> {:ok, ts, status}
+ result -> result
+ end
+ end)
+ end)
+ end
+
+ def set_last(user, domain, status) do
+ set_last(user, domain, status, now)
+ end
+
+ def set_last(user, domain, status, timestamp) do
+ Agent.update(@agent, fn last ->
+ Map.put(last, {user, domain}, {timestamp, status})
+ end)
+ end
+
+ ####################################################################
+ # Helpers
+ ####################################################################
+ def now() do
+ {megasecs, secs, _microsecs} = :os.timestamp
+ megasecs * 1000000 + secs
+ end
+
+ # TODO refactor: Move to ejabberd_test_mock
+ def mock(module, function, fun) do
+ try do
+ :meck.new(module)
+ catch
+ :error, {:already_started, _pid} -> :ok
+ end
+
+ :meck.expect(module, function, fun)
+ end
+
+end
diff --git a/test/mod_roster_mock.exs b/test/mod_roster_mock.exs
new file mode 100644
index 00000000..b4991cfd
--- /dev/null
+++ b/test/mod_roster_mock.exs
@@ -0,0 +1,192 @@
+ # mod_roster mock
+ ######################
+
+defmodule ModRosterMock do
+ @author "jsautret@process-one.net"
+
+ require Record
+ Record.defrecord :roster, Record.extract(:roster,
+ from: "mod_roster.hrl")
+
+ @agent __MODULE__
+
+ def init(domain, module) do
+ try do
+ Agent.stop(@agent)
+ catch
+ :exit, _e -> :ok
+ end
+
+ {:ok, _pid} = Agent.start_link(fn -> %{} end, name: @agent)
+
+ mock_with_moka module
+
+ #:mod_roster.stop(domain)
+ :mod_roster.start(domain, [])
+ end
+
+ def mock_with_moka(module) do
+ try do
+
+ module_mock = :moka.start(module)
+ :moka.replace(module_mock, :mod_roster, :invalidate_roster_cache,
+ fn (_user, _server) ->
+ :ok
+ end)
+
+ :moka.load(module_mock)
+
+ roster_mock = :moka.start(:mod_roster)
+
+ :moka.replace(roster_mock, :gen_mod, :db_type,
+ fn (_host, _opts) ->
+ {:none}
+ end)
+
+ :moka.replace(roster_mock, :gen_iq_handler, :add_iq_handler,
+ fn (_module, _host, _ns, _m, _f, _iqdisc) ->
+ :ok
+ end)
+
+ :moka.replace(roster_mock, :gen_iq_handler, :remove_iq_handler,
+ fn (_module, _host, _ns) ->
+ :ok
+ end)
+
+ :moka.replace(roster_mock, :transaction,
+ fn (_server, function) ->
+ {:atomic, function.()}
+ end)
+
+ :moka.replace(roster_mock, :get_roster,
+ fn (user, domain) ->
+ to_records(get_roster(user, domain))
+ end)
+
+ :moka.replace(roster_mock, :update_roster_t,
+ fn (user, domain, {u, d, _r}, item) ->
+ add_roster_item(user, domain, u<>"@"<>d,
+ roster(item, :name),
+ roster(item, :subscription),
+ roster(item, :groups),
+ roster(item, :ask),
+ roster(item, :askmessage))
+ end)
+
+ :moka.replace(roster_mock, :del_roster_t,
+ fn (user, domain, jid) ->
+ remove_roster_item(user, domain, :jlib.jid_to_string(jid))
+ end)
+
+ :moka.load(roster_mock)
+
+ catch
+ {:already_started, _pid} -> :ok
+ end
+
+ end
+
+ def mock_with_meck do
+# mock(:gen_mod, :db_type,
+# fn (_server, :mod_roster) ->
+# :mnesia
+# end)
+#
+# mock(:mnesia, :transaction,
+# fn (_server, function) ->
+# {:atomic, function.()}
+# end)
+#
+# mock(:mnesia, :write,
+# fn (Item) ->
+# throw Item
+# {:atomic, :ok}
+# end)
+
+ mock(:mod_roster, :transaction,
+ fn (_server, function) ->
+ {:atomic, function.()}
+ end)
+
+ mock(:mod_roster, :update_roster_t,
+ fn (user, domain, {u, d, _r}, item) ->
+ add_roster_item(user, domain, u<>"@"<>d,
+ roster(item, :name),
+ roster(item, :subscription),
+ roster(item, :groups),
+ roster(item, :ask),
+ roster(item, :askmessage))
+ end)
+
+ mock(:mod_roster, :invalidate_roster_cache,
+ fn (_user, _server) ->
+ :ok
+ end)
+
+ end
+
+ def add_roster_item(user, domain, jid, nick, subs \\ :none, groups \\ [],
+ ask \\ :none, askmessage \\ "")
+ when is_binary(user) and byte_size(user) > 0
+ and is_binary(domain) and byte_size(domain) > 0
+ and is_binary(jid) and byte_size(jid) > 0
+ and is_binary(nick)
+ and is_atom(subs)
+ and is_list(groups)
+ and is_atom(ask)
+ and is_binary(askmessage)
+ do
+ Agent.update(@agent, fn roster ->
+ Map.put(roster, {user, domain, jid}, %{nick: nick,
+ subs: subs, groups: groups,
+ ask: ask, askmessage: askmessage})
+ end)
+ end
+
+ def remove_roster_item(user, domain, jid) do
+ Agent.update(@agent, fn roster ->
+ Map.delete(roster, {user, domain, jid})
+ end)
+ end
+
+ def get_rosters() do
+ Agent.get(@agent, fn roster -> roster end)
+ end
+
+ def get_roster(user, domain) do
+ Agent.get(@agent, fn roster ->
+ for {u, d, jid} <- Map.keys(roster), u == user, d == domain,
+ do: {{u, d, jid}, Map.fetch!(roster, {u, d, jid})}
+ end)
+ end
+
+ def to_record({{user, domain, jid}, r}) do
+ roster(usj: {user, domain, jid},
+ us: {user, domain},
+ jid: :jlib.string_to_usr(jid),
+ subscription: r.subs,
+ ask: r.ask,
+ groups: r.groups,
+ askmessage: r.askmessage
+ )
+ end
+ def to_records(rosters) do
+ for item <- rosters, do: to_record(item)
+ end
+
+####################################################################
+# Helpers
+####################################################################
+
+ # TODO refactor: Move to ejabberd_test_mock
+ def mock(module, function, fun) do
+ try do
+ :meck.new(module, [:non_strict, :passthrough, :unstick])
+ catch
+ :error, {:already_started, _pid} -> :ok
+ end
+
+ :meck.expect(module, function, fun)
+ end
+
+end