diff options
author | Alexey Shchepin <alexey@process-one.net> | 2016-03-31 14:53:31 +0300 |
---|---|---|
committer | Alexey Shchepin <alexey@process-one.net> | 2016-03-31 14:53:31 +0300 |
commit | 3dc55c6d47e3093a6147ce275c7269a7d08ffc45 (patch) | |
tree | 1ff7eb63244a18f9c91dc26dd6e6845499f9f5b5 /test | |
parent | mix 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.exs | 79 | ||||
-rw-r--r-- | test/ejabberd_auth_mock.exs | 57 | ||||
-rw-r--r-- | test/ejabberd_commands_test.exs | 437 | ||||
-rw-r--r-- | test/ejabberd_hooks_test.exs | 6 | ||||
-rw-r--r-- | test/ejabberd_oauth_mock.exs | 30 | ||||
-rw-r--r-- | test/ejabberd_sm_mock.exs | 106 | ||||
-rw-r--r-- | test/elixir_SUITE.erl | 28 | ||||
-rw-r--r-- | test/mod_admin_extra_test.exs | 699 | ||||
-rw-r--r-- | test/mod_http_api_test.exs | 188 | ||||
-rw-r--r-- | test/mod_last_mock.exs | 65 | ||||
-rw-r--r-- | test/mod_roster_mock.exs | 192 |
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 |