From b70fcbd98ede34e3de175d2e20b144fd747d5aca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sara=20Aim=C3=A9e=20Smiseth?= <51710585+SaraSmiseth@users.noreply.github.com> Date: Fri, 30 Oct 2020 17:47:05 +0100 Subject: [PATCH] Create tests (#15) Created a tests folder which contains pytest and bats tests. Pytest is used to login and send messages to other accounts. Bats is used to check the log for debug messages. This fixes #13. --- .github/workflows/test.yml | 26 ++++++++ .gitignore | 5 +- .gitmodules | 9 +++ tests/bats/bats-assert | 1 + tests/bats/bats-core | 1 + tests/bats/bats-support | 1 + tests/docker-compose.yml | 24 ++++++++ tests/readme.md | 25 ++++++++ tests/requirements.txt | 3 + tests/test.bash | 45 ++++++++++++++ tests/test_prosody.py | 119 +++++++++++++++++++++++++++++++++++++ tests/tests.bats | 95 +++++++++++++++++++++++++++++ 12 files changed, 353 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test.yml create mode 100644 .gitmodules create mode 160000 tests/bats/bats-assert create mode 160000 tests/bats/bats-core create mode 160000 tests/bats/bats-support create mode 100644 tests/docker-compose.yml create mode 100644 tests/readme.md create mode 100644 tests/requirements.txt create mode 100755 tests/test.bash create mode 100644 tests/test_prosody.py create mode 100644 tests/tests.bats diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..483ac68 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,26 @@ +name: Test + +on: + pull_request: + branches: dev + push: + branches: dev + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Checkout submodules + uses: textbook/git-checkout-submodule-action@master + + - name: install python3-venv + run: sudo apt-get install python3-venv + + - name: build test image + run: docker build . -t prosody + + - name: run tests + run: cd ./tests/ && ./test.bash diff --git a/.gitignore b/.gitignore index 07f43b8..9f0f847 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ -data/* \ No newline at end of file +data/* +tests/certs/ +tests/venv/ +tests/__pycache__/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4608d8c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "tests/bats/bats-support"] + path = tests/bats/bats-support + url = https://github.com/bats-core/bats-support.git +[submodule "tests/bats/bats-core"] + path = tests/bats/bats-core + url = https://github.com/bats-core/bats-core.git +[submodule "tests/bats/bats-assert"] + path = tests/bats/bats-assert + url = https://github.com/bats-core/bats-assert.git diff --git a/tests/bats/bats-assert b/tests/bats/bats-assert new file mode 160000 index 0000000..0a8dd57 --- /dev/null +++ b/tests/bats/bats-assert @@ -0,0 +1 @@ +Subproject commit 0a8dd57e2cc6d4cc064b1ed6b4e79b9f7fee096f diff --git a/tests/bats/bats-core b/tests/bats/bats-core new file mode 160000 index 0000000..8fb853a --- /dev/null +++ b/tests/bats/bats-core @@ -0,0 +1 @@ +Subproject commit 8fb853a6cbc0169958707381985f3cd59789ccb1 diff --git a/tests/bats/bats-support b/tests/bats/bats-support new file mode 160000 index 0000000..d140a65 --- /dev/null +++ b/tests/bats/bats-support @@ -0,0 +1 @@ +Subproject commit d140a65044b2d6810381935ae7f0c94c7023c8c3 diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml new file mode 100644 index 0000000..c04c41b --- /dev/null +++ b/tests/docker-compose.yml @@ -0,0 +1,24 @@ +version: '3.7' + +services: + prosody: + image: prosody + restart: unless-stopped + ports: + - "5000:5000" + - "5222:5222" + - "5223:5223" + - "5269:5269" + - "5281:5281" + environment: + DOMAIN: localhost + E2E_POLICY_WHITELIST: "admin@localhost, user1@localhost" + LOG_LEVEL: debug + PROSODY_ADMINS: "admin@localhost, admin2@localhost" + extra_hosts: + - "conference.localhost:127.0.0.1" + - "pubsub.localhost:127.0.0.1" + - "proxy.localhost:127.0.0.1" + - "upload.localhost:127.0.0.1" + volumes: + - ./certs:/usr/local/etc/prosody/certs diff --git a/tests/readme.md b/tests/readme.md new file mode 100644 index 0000000..9a6644a --- /dev/null +++ b/tests/readme.md @@ -0,0 +1,25 @@ +# Tests + +## Dependencies + +* docker +* docker-compose +* python 3 + +## Run tests + +Execute [`test.bash`](test.bash). + +## Upgrade python packages + +The following will install the newest version of packages in requirements.txt. + +``` bash +cat requirements.txt | sed 's/==.*//g' | xargs pip install -U +``` + +If updates are available --> update and create new version with: + +``` bash +pip-chill > requirements.txt +``` diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..bcd35ba --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,3 @@ +aioxmpp==0.11.0 +pip-chill==1.0.0 +pytest-asyncio==0.14.0 diff --git a/tests/test.bash b/tests/test.bash new file mode 100755 index 0000000..75048cd --- /dev/null +++ b/tests/test.bash @@ -0,0 +1,45 @@ +#!/bin/bash + +set -e + +# generate certs for testing + +generateCert() { + DOMAIN="$1" + if [[ ! -d certs/"$DOMAIN" ]] ; then + mkdir -p certs/"$DOMAIN" + cd certs/"$DOMAIN" + openssl req -x509 -newkey rsa:4096 -keyout privkey.pem -out fullchain.pem -days 365 -subj "/CN=$DOMAIN" -nodes + chmod 777 *.pem + cd ../../ + fi +} + +generateCert "localhost" +generateCert "conference.localhost" +generateCert "proxy.localhost" +generateCert "pubsub.localhost" +generateCert "upload.localhost" + +sudo docker-compose down \ +&& sudo docker-compose up -d \ +\ +&& sudo docker exec tests_prosody_1 /bin/bash -c "/entrypoint.sh register admin localhost 12345678" \ +&& sudo docker exec tests_prosody_1 /bin/bash -c "/entrypoint.sh register user1 localhost 12345678" \ +&& sudo docker exec tests_prosody_1 /bin/bash -c "/entrypoint.sh register user2 localhost 12345678" \ +&& sudo docker exec tests_prosody_1 /bin/bash -c "/entrypoint.sh register user3 localhost 12345678" \ +\ +&& python --version \ +&& python3 --version \ +&& python3 -m venv venv \ +&& source venv/bin/activate \ +&& python --version \ +&& pip --version \ +&& pip install -r requirements.txt \ +&& pytest \ +&& deactivate \ +&& sleep 5 \ +&& sudo docker-compose logs \ +&& ./bats/bats-core/bin/bats tests.bats + +sudo docker-compose down diff --git a/tests/test_prosody.py b/tests/test_prosody.py new file mode 100644 index 0000000..e6c39bc --- /dev/null +++ b/tests/test_prosody.py @@ -0,0 +1,119 @@ +import aiosasl +import aioxmpp +import aioxmpp.dispatcher +import asyncio +import pytest + +@pytest.fixture +def client(client_username, password): + + jid = aioxmpp.JID.fromstr(client_username) + + client = aioxmpp.PresenceManagedClient( + jid, + aioxmpp.make_security_layer( + password, + no_verify=True + ), + ) + return client + +@pytest.fixture +def client_with_message_dispatcher(client): + def message_received(msg): + print(msg) + print(msg.body) + assert msg.body == "Hello World!" + + # obtain an instance of the service + message_dispatcher = client.summon( + aioxmpp.dispatcher.SimpleMessageDispatcher + ) + + # register a message callback here + message_dispatcher.register_callback( + aioxmpp.MessageType.CHAT, + None, + message_received, + ) + return client + +@pytest.mark.asyncio +@pytest.mark.parametrize("client_username, password", [("admin@localhost", "12345678")]) +async def test_send_message_from_admin_to_user1(client): + recipient_jid = aioxmpp.JID.fromstr("user1@localhost") + async with client.connected() as stream: + msg = aioxmpp.Message( + to=recipient_jid, + type_=aioxmpp.MessageType.CHAT, + ) + # None is for "default language" + msg.body[None] = "Hello World!" + + await client.send(msg) + +@pytest.mark.asyncio +@pytest.mark.parametrize("client_username, password", [("admin@localhost", "12345678")]) +async def test_send_message_from_admin_to_user2(client): + recipient_jid = aioxmpp.JID.fromstr("user2@localhost") + async with client.connected() as stream: + msg = aioxmpp.Message( + to=recipient_jid, + type_=aioxmpp.MessageType.CHAT, + ) + msg.body[None] = "Hello World!" + + await client.send(msg) + +@pytest.mark.asyncio +@pytest.mark.parametrize("client_username, password", [("user1@localhost", "12345678")]) +async def test_send_message_from_user1_to_user2(client): + recipient_jid = aioxmpp.JID.fromstr("user2@localhost") + async with client.connected() as stream: + msg = aioxmpp.Message( + to=recipient_jid, + type_=aioxmpp.MessageType.CHAT, + ) + msg.body[None] = "Hello World!" + + await client.send(msg) + +@pytest.mark.asyncio +@pytest.mark.parametrize("client_username, password", [("user2@localhost", "12345678")]) +async def test_send_message_from_user2_to_user3(client): + recipient_jid = aioxmpp.JID.fromstr("user3@localhost") + async with client.connected() as stream: + msg = aioxmpp.Message( + to=recipient_jid, + type_=aioxmpp.MessageType.CHAT, + ) + msg.body[None] = "Hello World!" + + await client.send(msg) + +@pytest.mark.asyncio +@pytest.mark.parametrize("client_username, password", [("user2@localhost", "12345678")]) +async def test_send_message_from_user2_to_nonexisting(client): + recipient_jid = aioxmpp.JID.fromstr("nonexisting@localhost") + async with client.connected() as stream: + msg = aioxmpp.Message( + to=recipient_jid, + type_=aioxmpp.MessageType.CHAT, + ) + msg.body[None] = "Hello World!" + + await client.send(msg) + +@pytest.mark.asyncio +@pytest.mark.parametrize("client_username, password", [("user2@localhost", "wrong password")]) +async def test_can_not_log_in_with_wrong_password(client): + with pytest.raises(aiosasl.AuthenticationFailure): + recipient_jid = aioxmpp.JID.fromstr("nonexisting@localhost") + async with client.connected() as stream: + msg = aioxmpp.Message( + to=recipient_jid, + type_=aioxmpp.MessageType.CHAT, + ) + msg.body[None] = "Hello World!" + + await client.send(msg) diff --git a/tests/tests.bats b/tests/tests.bats new file mode 100644 index 0000000..f9f8df2 --- /dev/null +++ b/tests/tests.bats @@ -0,0 +1,95 @@ +# For tests with pipes see: https://github.com/sstephenson/bats/issues/10 + +load 'bats/bats-support/load' +load 'bats/bats-assert/load' + +# group alternation in regex because the xml properties switch around. sometimes 'type=...' comes after 'to=...' and sometimes before +@test "Should send 5 messages" { + run bash -c "sudo docker-compose logs | grep -E \"Received\[c2s\]: \" | wc -l" + assert_success + assert_output "5" +} + +@test "Should select certificate for localhost" { + run bash -c "sudo docker-compose logs | grep \"Selecting certificate /usr/local/etc/prosody/certs/localhost/fullchain.pem with key /usr/local/etc/prosody/certs/localhost/privkey.pem for localhost\" | wc -l" + assert_success + assert_output "3" +} + +@test "Should select certificate for conference.localhost" { + run bash -c "sudo docker-compose logs | grep \"Selecting certificate /usr/local/etc/prosody/certs/conference.localhost/fullchain.pem with key /usr/local/etc/prosody/certs/conference.localhost/privkey.pem for conference.localhost\" | wc -l" + assert_success + assert_output "3" +} + +@test "Should select certificate for proxy.localhost" { + run bash -c "sudo docker-compose logs | grep \"Selecting certificate /usr/local/etc/prosody/certs/proxy.localhost/fullchain.pem with key /usr/local/etc/prosody/certs/proxy.localhost/privkey.pem for proxy.localhost\" | wc -l" + assert_success + assert_output "3" +} + +@test "Should select certificate for pubsub.localhost" { + run bash -c "sudo docker-compose logs | grep \"Selecting certificate /usr/local/etc/prosody/certs/pubsub.localhost/fullchain.pem with key /usr/local/etc/prosody/certs/pubsub.localhost/privkey.pem for pubsub.localhost\" | wc -l" + assert_success + assert_output "3" +} + +@test "Should select certificate for upload.localhost" { + run bash -c "sudo docker-compose logs | grep \"Selecting certificate /usr/local/etc/prosody/certs/upload.localhost/fullchain.pem with key /usr/local/etc/prosody/certs/upload.localhost/privkey.pem for upload.localhost\" | wc -l" + assert_success + assert_output "3" +} + +@test "Should log error for user with wrong password" { + run bash -c "sudo docker-compose logs | grep \"Session closed by remote with error: undefined-condition (user intervention: authentication failed: authentication aborted by user)\"" + assert_success + assert_output +} + +@test "Should activate s2s" { + run bash -c "sudo docker-compose logs | grep -E \"Activated service 's2s' on (\[::\]:5269|\[\*\]:5269), (\[::\]:5269|\[\*\]:5269)\"" + assert_success + assert_output +} + +@test "Should activate c2s" { + run bash -c "sudo docker-compose logs | grep -E \"Activated service 'c2s' on (\[::\]:5222|\[\*\]:5222), (\[::\]:5222|\[\*\]:5222)\"" + assert_success + assert_output +} + +@test "Should activate legacy_ssl" { + run bash -c "sudo docker-compose logs | grep -E \"Activated service 'legacy_ssl' on (\[::\]:5223|\[\*\]:5223), (\[::\]:5223|\[\*\]:5223)\"" + assert_success + assert_output +} + +@test "Should activate proxy65" { + run bash -c "sudo docker-compose logs | grep -E \"Activated service 'proxy65' on (\[::\]:5000|\[\*\]:5000), (\[::\]:5000|\[\*\]:5000)\"" + assert_success + assert_output +} + +@test "Should activate http" { + run bash -c "sudo docker-compose logs | grep -E \"Activated service 'http' on (\[::\]:5280|\[\*\]:5280), (\[::\]:5280|\[\*\]:5280)\"" + assert_success + assert_output +} + +@test "Should activate https" { + run bash -c "sudo docker-compose logs | grep -E \"Activated service 'https' on (\[::\]:5281|\[\*\]:5281), (\[::\]:5281|\[\*\]:5281)\"" + assert_success + assert_output +} + +@test "Should load module cloud_notify" { + run bash -c "sudo docker-compose logs | grep \"localhost:cloud_notify.*info.*Module loaded\"" + assert_success + assert_output +} + +@test "Should show upload URL" { + run bash -c "sudo docker-compose logs | grep \"URL: - Ensure this can be reached by users\"" + assert_success + assert_output +}