diff --git a/.dockerignore b/.dockerignore index bab75833..3e14f385 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ * +!.env !.yamllint !*.yml !docker/ diff --git a/README.rst b/README.rst index 52563d0b..c4155ae7 100644 --- a/README.rst +++ b/README.rst @@ -67,11 +67,9 @@ Development setup First, install the system dependencies: -- `curl `_ - `docker `_ - `docker-compose `_ - `git `_ -- `jq `_ - `make `_ Second, get the source code. diff --git a/docker-compose.yml b/docker-compose.yml index 5a7ae372..5217229a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -95,7 +95,16 @@ services: build: context: . dockerfile: docker/setup/Dockerfile - command: /app/setup.sh --help + command: echo skipped + + integtest: + image: ${DOCKER_REPO}/opwenserver_integtest:${BUILD_TAG} + build: + context: . + dockerfile: docker/integtest/Dockerfile + command: echo skipped + volumes: + - /var/run/docker.sock:/var/run/docker.sock rabbitmq: image: rabbitmq:management-alpine @@ -120,9 +129,7 @@ services: - ${POSTGRES_PORT}:5432 azurite: - image: arafato/azurite:2.6.5 - environment: - executable: blob + image: mcr.microsoft.com/azure-storage/azurite:latest ports: - ${AZURITE_PORT}:10000 diff --git a/tests/integration/wait.sh b/docker/integtest/0-wait-for-services.sh similarity index 59% rename from tests/integration/wait.sh rename to docker/integtest/0-wait-for-services.sh index d9243c4f..e3ef856d 100755 --- a/tests/integration/wait.sh +++ b/docker/integtest/0-wait-for-services.sh @@ -1,33 +1,18 @@ #!/usr/bin/env bash set -euo pipefail -readonly polling_interval_seconds=2 - -log() { - echo "$@" >&2 -} +scriptdir="$(dirname "$0")" +# shellcheck disable=SC1090 +. "${scriptdir}/utils.sh" -get_dotenv() { - local key dotenv_file - - key="$1" - dotenv_file="$(dirname "$0")/../../.env" - - grep "^${key}=" "${dotenv_file}" | cut -d'=' -f2- -} +readonly polling_interval_seconds=2 -sql() { - local user database query +wait_for_rabbitmq() { + local rabbitmq - user="$(get_dotenv "POSTGRES_USER")" - database="$(get_dotenv "POSTGRES_DB")" - query="$1" + rabbitmq="$(get_container rabbitmq)" - docker-compose exec postgres psql -U "${user}" -d "${database}" -c "${query}" -} - -wait_for_rabbitmq() { - while ! docker-compose exec rabbitmq rabbitmqctl wait -q -P 1 -t "${polling_interval_seconds}"; do + while ! docker exec "${rabbitmq}" rabbitmqctl wait -q -P 1 -t "${polling_interval_seconds}"; do log "Waiting for rabbitmq" done diff --git a/tests/integration/0-register-client.sh b/docker/integtest/1-register-client.sh similarity index 76% rename from tests/integration/0-register-client.sh rename to docker/integtest/1-register-client.sh index f40f4794..15e621bc 100755 --- a/tests/integration/0-register-client.sh +++ b/docker/integtest/1-register-client.sh @@ -1,8 +1,11 @@ #!/usr/bin/env bash set -euo pipefail -out_dir="$(dirname "$0")/../files/end_to_end/test.out" +scriptdir="$(dirname "$0")" +out_dir="${scriptdir}/files/test.out" mkdir -p "${out_dir}" +# shellcheck disable=SC1090 +. "${scriptdir}/utils.sh" # workflow 3: register a new client called "developer" # normally this endpoint would be called during a new lokole device setup @@ -10,7 +13,7 @@ curl -fs \ -H "Content-Type: application/json" \ -u "admin:password" \ -d '{"domain":"developer1.lokole.ca"}' \ - "http://localhost:8080/api/email/register/" \ + "http://nginx:8888/api/email/register/" \ | tee "${out_dir}/register1.json" # registering a client with bad credentials should fail @@ -18,7 +21,7 @@ if curl -fs \ -H "Content-Type: application/json" \ -u "baduser:badpassword" \ -d '{"domain":"hacker.lokole.ca"}' \ - "http://localhost:8080/api/email/register/" \ + "http://nginx:8888/api/email/register/" \ ; then echo "Was able to register a client with bad credentials" >&2; exit 4; fi # also register another client to simulate multi-client emails @@ -26,5 +29,5 @@ curl -fs \ -H "Content-Type: application/json" \ -u "admin:password" \ -d '{"domain":"developer2.lokole.ca"}' \ - "http://localhost:8080/api/email/register/" \ + "http://nginx:8888/api/email/register/" \ | tee "${out_dir}/register2.json" diff --git a/tests/integration/1-client-uploads-emails.sh b/docker/integtest/2-client-uploads-emails.sh similarity index 60% rename from tests/integration/1-client-uploads-emails.sh rename to docker/integtest/2-client-uploads-emails.sh index 9eeff23f..3de0a3b8 100755 --- a/tests/integration/1-client-uploads-emails.sh +++ b/docker/integtest/2-client-uploads-emails.sh @@ -1,9 +1,12 @@ #!/usr/bin/env bash set -euo pipefail -in_dir="$(dirname "$0")/../files/end_to_end" -out_dir="$(dirname "$0")/../files/end_to_end/test.out" +scriptdir="$(dirname "$0")" +in_dir="${scriptdir}/files" +out_dir="${scriptdir}/files/test.out" mkdir -p "${out_dir}" +# shellcheck disable=SC1090 +. "${scriptdir}/utils.sh" emails_to_send="${in_dir}/client-emails.tar.gz" client_id="$(jq -r '.client_id' < "${out_dir}/register1.json")" @@ -12,14 +15,10 @@ resource_id="$(uuidgen).tar.gz" # workflow 1: send emails written on the client to the world # first we simulate the client uploading its emails to the shared blob storage -curl -fs \ - -X PUT -T "${emails_to_send}" \ - -H "x-ms-blob-type: BlockBlob" \ - -H "Content-Length: $(wc -c "${emails_to_send}" | cut -d' ' -f1)" \ - "http://localhost:10000/devstoreaccount1/${resource_container}/${resource_id}" +az_storage upload "${resource_container}" "${resource_id}" "${emails_to_send}" # the client then calls the server to trigger the delivery of the emails curl -fs \ -H "Content-Type: application/json" \ -d '{"resource_id":"'"${resource_id}"'"}' \ - "http://localhost:8080/api/email/upload/${client_id}" + "http://nginx:8888/api/email/upload/${client_id}" diff --git a/docker/integtest/3-receive-email-for-client.sh b/docker/integtest/3-receive-email-for-client.sh new file mode 100755 index 00000000..148e20d4 --- /dev/null +++ b/docker/integtest/3-receive-email-for-client.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +scriptdir="$(dirname "$0")" +in_dir="${scriptdir}/files" +out_dir="${scriptdir}/files/test.out" +mkdir -p "${out_dir}" +# shellcheck disable=SC1090 +. "${scriptdir}/utils.sh" + +email_to_receive="${in_dir}/inbound-email.mime" + +client_id="$(jq -r '.client_id' < "${out_dir}/register1.json")" + +# workflow 2a: receive an email directed at one of the clients +# this simulates sendgrid delivering an email to the service +http --check-status -f POST \ + "http://nginx:8888/api/email/sendgrid/${client_id}" \ + "email=@${email_to_receive}" + +# simulate delivery of the same email to the second mailbox +http --check-status -f POST \ + "http://nginx:8888/api/email/sendgrid/${client_id}" \ + "email=@${email_to_receive}" diff --git a/tests/integration/3-client-downloads-emails.sh b/docker/integtest/4-client-downloads-emails.sh similarity index 80% rename from tests/integration/3-client-downloads-emails.sh rename to docker/integtest/4-client-downloads-emails.sh index 4488aee2..67994a6e 100755 --- a/tests/integration/3-client-downloads-emails.sh +++ b/docker/integtest/4-client-downloads-emails.sh @@ -1,8 +1,11 @@ #!/usr/bin/env bash set -euo pipefail -out_dir="$(dirname "$0")/../files/end_to_end/test.out" +scriptdir="$(dirname "$0")" +out_dir="${scriptdir}/files/test.out" mkdir -p "${out_dir}" +# shellcheck disable=SC1090 +. "${scriptdir}/utils.sh" for i in 1 2; do @@ -15,15 +18,13 @@ resource_container="$(jq -r '.resource_container' < "${out_dir}/register${i}.jso # will package up the emails and store them on the shared blob storage curl -fs \ -H "Accept: application/json" \ - "http://localhost:8080/api/email/download/${client_id}" \ + "http://nginx:8888/api/email/download/${client_id}" \ | tee "${out_dir}/download${i}.json" resource_id="$(jq -r '.resource_id' < "${out_dir}/download${i}.json")" # now we simulate the client downloading the package from the shared blob storage -curl -fs \ - "http://localhost:10000/devstoreaccount1/${resource_container}/${resource_id}" \ -> "${out_dir}/downloaded${i}.tar.gz" +az_storage download "${resource_container}" "${resource_id}" "${out_dir}/downloaded${i}.tar.gz" tar xzf "${out_dir}/downloaded${i}.tar.gz" -C "${out_dir}" diff --git a/tests/integration/assert.sh b/docker/integtest/5-assert-on-results.sh similarity index 65% rename from tests/integration/assert.sh rename to docker/integtest/5-assert-on-results.sh index a1e92f45..bb705c68 100755 --- a/tests/integration/assert.sh +++ b/docker/integtest/5-assert-on-results.sh @@ -1,13 +1,11 @@ #!/usr/bin/env bash set -euo pipefail -out_dir="$(dirname "$0")/../files/end_to_end/test.out" +scriptdir="$(dirname "$0")" +out_dir="${scriptdir}/files/test.out" mkdir -p "${out_dir}" - -sql_query() { - docker-compose exec postgres psql -Aqt -c "$1" -U ascoderu -d telemetry \ - | tr -d -C '0-9' -} +# shellcheck disable=SC1090 +. "${scriptdir}/utils.sh" num_exceptions="$(sql_query 'select count(*) from exceptions;')" num_exceptions_expected=0 diff --git a/docker/integtest/Dockerfile b/docker/integtest/Dockerfile new file mode 100644 index 00000000..c56cb266 --- /dev/null +++ b/docker/integtest/Dockerfile @@ -0,0 +1,17 @@ +FROM microsoft/azure-cli:2.0.32 + +RUN apk add -q --no-cache \ + curl=7.59.0-r0 \ + docker=1.11.2-r1 \ + jq=1.5-r2 \ + util-linux=2.28-r3 + +RUN pip3 install --no-cache-dir --upgrade pip==19.2.3 && \ + pip3 install --no-cache-dir httpie==1.0.2 + +WORKDIR /app + +COPY .env . +COPY docker/integtest/ ./ + +CMD ["./tests.sh"] diff --git a/tests/files/end_to_end/.gitignore b/docker/integtest/files/.gitignore similarity index 100% rename from tests/files/end_to_end/.gitignore rename to docker/integtest/files/.gitignore diff --git a/tests/files/end_to_end/client-emails.tar.gz b/docker/integtest/files/client-emails.tar.gz similarity index 100% rename from tests/files/end_to_end/client-emails.tar.gz rename to docker/integtest/files/client-emails.tar.gz diff --git a/tests/files/end_to_end/inbound-email.mime b/docker/integtest/files/inbound-email.mime similarity index 100% rename from tests/files/end_to_end/inbound-email.mime rename to docker/integtest/files/inbound-email.mime diff --git a/docker/integtest/tests.sh b/docker/integtest/tests.sh new file mode 100755 index 00000000..a305ff4e --- /dev/null +++ b/docker/integtest/tests.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +scriptdir="$(dirname "$0")" + +"${scriptdir}/0-wait-for-services.sh" +"${scriptdir}/1-register-client.sh" +"${scriptdir}/2-client-uploads-emails.sh" && sleep 10s +"${scriptdir}/3-receive-email-for-client.sh" && sleep 10s +"${scriptdir}/4-client-downloads-emails.sh" +"${scriptdir}/5-assert-on-results.sh" + +rm -rf "${scriptdir}/files/test.out" diff --git a/docker/integtest/utils.sh b/docker/integtest/utils.sh new file mode 100755 index 00000000..78adf202 --- /dev/null +++ b/docker/integtest/utils.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -euo pipefail + +sql() { + local postgres user database query + + postgres="$(get_container postgres)" + user="$(get_dotenv "POSTGRES_USER")" + database="$(get_dotenv "POSTGRES_DB")" + query="$1" + + docker exec "${postgres}" psql -U "${user}" -d "${database}" -c "${query}" +} + +sql_query() { + local postgres + + postgres="$(get_container postgres)" + + docker exec "${postgres}" psql -Aqt -c "$1" -U ascoderu -d telemetry \ + | tr -d -C '0-9' +} + +get_container() { + docker ps --format '{{.Names}}' | grep "$1" +} + +get_dotenv() { + local key dotenv_file + + key="$1" + dotenv_file="$(dirname "$0")/.env" + + grep "^${key}=" "${dotenv_file}" | cut -d'=' -f2- +} + +log() { + echo "$@" >&2 +} + +az_storage() { + local mode="$1" + local container="$2" + local blob="$3" + local file="$4" + local storage_account storage_key + + storage_account="$(get_dotenv AZURITE_ACCOUNT)" + storage_key="$(get_dotenv AZURITE_KEY)" + + az storage blob "${mode}" --no-progress \ + --file "${file}" \ + --name "${blob}" \ + --container-name "${container}" \ + --connection-string "DefaultEndpointsProtocol=http;AccountName=${storage_account};AccountKey=${storage_key};BlobEndpoint=http://azurite:10000/${storage_account};" \ + > /dev/null +} diff --git a/makefile b/makefile index 81f55110..fa73d0cc 100644 --- a/makefile +++ b/makefile @@ -53,13 +53,8 @@ lint: lint-python lint-shell lint-swagger lint-docker lint-yaml ci: tests lint integration-tests: - ./tests/integration/wait.sh && \ - ./tests/integration/0-register-client.sh && \ - ./tests/integration/1-client-uploads-emails.sh && sleep 10s && \ - ./tests/integration/2-receive-email-for-client.sh && sleep 10s && \ - ./tests/integration/3-client-downloads-emails.sh && \ - ./tests/integration/assert.sh && \ - rm -rf tests/files/end_to_end/test.out + docker-compose build integtest && \ + docker-compose run --rm integtest ./tests.sh clean: find . -name '__pycache__' -type d -print0 | xargs -0 rm -rf @@ -82,7 +77,7 @@ logs: fi stop: - docker-compose down --volumes + docker-compose down --volumes --timeout=5 verify-build: docker pull wagoodman/dive diff --git a/tests/integration/2-receive-email-for-client.sh b/tests/integration/2-receive-email-for-client.sh deleted file mode 100755 index 8bc56002..00000000 --- a/tests/integration/2-receive-email-for-client.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -in_dir="$(dirname "$0")/../files/end_to_end" -out_dir="$(dirname "$0")/../files/end_to_end/test.out" -mkdir -p "${out_dir}" - -email_to_receive="${in_dir}/inbound-email.mime" - -client_id="$(jq -r '.client_id' < "${out_dir}/register1.json")" - -# workflow 2a: receive an email directed at one of the clients -# this simulates sendgrid delivering an email to the service -curl -fs \ - -H "Content-Type: multipart/form-data" \ - -F "email=$(cat "${email_to_receive}")" \ - "http://localhost:8080/api/email/sendgrid/${client_id}" - -# simulate delivery of the same email to the second mailbox -curl -fs \ - -H "Content-Type: multipart/form-data" \ - -F "email=$(cat "${email_to_receive}")" \ - "http://localhost:8080/api/email/sendgrid/${client_id}"