Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: Run integration tests
on:
pull_request:

jobs:
integration_tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cd integration_tests && make dctest
11 changes: 9 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
services:
db:
image: postgres:17
hostname: db
image: postgres:18
restart: always
environment:
POSTGRES_DB: elekto
POSTGRES_USER: root
POSTGRES_PASSWORD: root
volumes:
- pgdata:/var/lib/postgresql/data
- pgdata:/var/lib/postgresql
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ports:
- '5432:5432'
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}" ]
interval: 10s
retries: 5
start_period: 30s
timeout: 10s

volumes:
pgdata:
15 changes: 11 additions & 4 deletions elekto/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,23 @@
# limitations under the License.
#
# Author(s): Manish Sahani <rec.manish.sahani@gmail.com>
import os

# Application's CSRF Security
CSRF_STATE = 'state'
AUTH_STATE = 'authentication'

# Github Endpoints
GITHUB_AUTHORIZE = 'https://github.com/login/oauth/authorize'
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These values effectively remain unchanged as long as there is no INTEGRATION_TEST env variable set to true.

GITHUB_ACCESS = 'https://github.com/login/oauth/access_token'
GITHUB_PROFILE = 'https://api.github.com/user'
# GitHub Endpoints
# TODO: Make the GitHub externally configurable/overridable. If not specified in env, use github.com
github_host = 'https://github.com'
# github_host = 'http://localhost:9000'

if os.environ.get('INTEGRATION_TEST') == 'true':
github_host = 'http://github:9000'

GITHUB_AUTHORIZE = f'{github_host}/login/oauth/authorize'
GITHUB_ACCESS = f'{github_host}/login/oauth/access_token'
GITHUB_PROFILE = f'{github_host}/user'

# Election attributes related constants
ELEC_STAT_COMPLETED = 'completed'
Expand Down
2 changes: 1 addition & 1 deletion entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ if [ "$APP_DEBUG" == "True" ]; then
echo "in Debug mode"
./console --run
else
if [ $APP_CONNECT == "socket" ]; then
if [ "$APP_CONNECT" == "socket" ]; then
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC I had to wrap this in tags because the elekto service wouldn't start in the integration test stack without these quotes. I'm not a bash specialist, but my impression is this change is fine.

# socket mode for fronting by nginx
echo "with a socket connection on $APP_PORT"
uwsgi --module elekto:APP --processes 8 --socket :$APP_PORT
Expand Down
12 changes: 12 additions & 0 deletions integration_tests/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.11

WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt
RUN playwright install
RUN playwright install-deps

COPY tests tests

CMD ["pytest", "tests"]
19 changes: 19 additions & 0 deletions integration_tests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
PYTEST:=./venv/bin/pytest
PIP:=./venv/bin/pip

.PHONY: clean
clean:
rm -rf ./venv

.PHONY: venv
venv: clean
python3.11 -m venv venv
$(PIP) install -r requirements.txt

.PHONY: test
test:
$(PYTEST)

.PHONY: dctest
dctest:
docker compose --profile test up --abort-on-container-exit --exit-code-from integration_tests
30 changes: 30 additions & 0 deletions integration_tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Elekto integration tests
This directory contains the integration tests for Elekto. These test focus on the integration with Github and the way
Github usernames flow through the Elekto application.

## Running tests
The setup is not bootstrapped (yet), so various manual steps are required to run the required local infra. Then the
integration tests can be run.

### Infra
These tests require the following to be running:
- elekto
- Change the Github endpoints in `elekto/constants.py` to:
- GITHUB_AUTHORIZE = 'http://localhost:9000/login/oauth/authorize'
- GITHUB_ACCESS = 'http://localhost:9000/login/oauth/access_token'
- GITHUB_PROFILE = 'http://localhost:9000/user'
- Start with `python console --run` (in the Elekto project).
- Optionally also do `docker compose up` if you want to use the Postgres database it provides.
- github-static-mock
- https://github.com/oduludo/github-oauth-mock
- Start the required Redis server with `docker compose up` in the github-oauth-mock project.
- Install dependencies with `poetry install`.
- Start the mock server with `poetry run start`.

### Tests
Tests can be run from the `elekto/integration_tests` directory. Tests runner is Pytest, headless browser testing is done
using Playwright. A virtual environment is required to run the tests. Tests assume all infra runs at the default ports.
- Create the virtual env with `make venv`. This will also install dependencies.
- Run tests with `make test`.

To run tests entirely in a Docker Compose setup, use `make dctest`.
51 changes: 51 additions & 0 deletions integration_tests/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
include:
- ../docker-compose.yml # Defines the `db` service
- ./services/github-static-mock/docker-compose.yaml # Defines the `github` mock backend service

services:
integration_tests:
build: .
environment:
ELEKTO_HOST: "elekto:8000"
GITHUB_HOST: "github:9000"
depends_on:
elekto:
condition: service_healthy
db:
condition: service_healthy
profiles:
- test
volumes:
- "./tests:/app/tests"
elekto:
build: ../
user: "root" # To access /app/meta/.git/FETCH_HEAD
ports:
# Links will point to `http://elekto:8000/`, so you must manually patch the URLs to localhost in your browser
# after each redirect. Exposing the Elekto service allows for manual checking during tests if desired.
- "8000:8000"
environment:
DB_CONNECTION: postgresql
DB_HOST: db
DB_PORT: 5432
DB_PASSWORD: root
DB_DATABASE: elekto
INTEGRATION_TEST: true
APP_PORT: 8000
META_REPO: https://github.com/elekto-io/elekto.meta.test.git
ELECTION_DIR: elections
META_DEPLOYMENT: local
META_PATH: meta
META_BRANCH: main
META_SECRET: xxx
healthcheck:
test: [ "CMD", "curl", "-f", "http://0.0.0.0:8000/app/" ]
interval: 10s
retries: 5
start_period: 5s
timeout: 10s
depends_on:
db:
condition: service_healthy
profiles:
- test
28 changes: 28 additions & 0 deletions integration_tests/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
attrs==25.4.0
certifi==2025.10.5
charset-normalizer==3.4.3
greenlet==3.2.4
h11==0.16.0
idna==3.10
iniconfig==2.1.0
outcome==1.3.0.post0
packaging==25.0
playwright==1.55.0
pluggy==1.6.0
pyee==13.0.0
Pygments==2.19.2
PySocks==1.7.1
pytest==8.4.2
pytest-base-url==2.1.0
pytest-playwright==0.7.1
python-slugify==8.0.4
requests==2.32.5
sniffio==1.3.1
sortedcontainers==2.4.0
text-unidecode==1.3
trio==0.31.0
trio-websocket==0.12.2
typing_extensions==4.15.0
urllib3==2.5.0
websocket-client==1.9.0
wsproto==1.2.0
13 changes: 13 additions & 0 deletions integration_tests/services/github-static-mock/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3.13

WORKDIR /app

# Install Poetry in a place that's already in $PATH
ENV POETRY_HOME=/usr/local
RUN curl -sSL https://install.python-poetry.org | python3 -

COPY . .
RUN poetry install

EXPOSE 9000
CMD ["poetry", "run", "start"]
55 changes: 55 additions & 0 deletions integration_tests/services/github-static-mock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Static oauth GitHub mock
This project mocks GitHub oauth server's [web application flow](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#web-application-flow).
As this is a static project, codes and tokens are hardcoded. An endpoint is available to set the user data to be
returned on the next call to the `/user` endpoint. By doing this, a webpage with form input on the mocked user is
avoided, keeping this simple for automated tests.

WARNING: This is a project for testing purposes only. Nothing about this implementation is secure.

## Running the project
Currently only the Redis cache runs on Docker Compose. This stores the upcoming user's data. Run it with
`docker compose up`.

The web application itself can be run with `poetry run start` after running `poetry install`.

## Endpoints
### Authorization mechanics
The oauth endpoints mocked facilitate the web application flow. Calls in order are:
1. Call `/system/upcoming-user` to set the next mocked user data.
2. Call `/login/oauth/authorize` to have the application return a response with a code and state.
3. Call `/login/oauth/access_token` to mock exchanging the code for a bearer token.
4. Call `/user` to obtain user data.

## /system/upcoming-user
Example call (using [HTTPie](https://httpie.io/)):

```shell
http "http://localhost:9000/system/upcoming-user" name="John Smith" login=jsmith
```

## /login/oauth/authorize
Authorization requires query parameters: response_type, client_id, scope, state and redirect_uri. The redirect_uri is
used to redirect the client. The state is included in the redirect URI for further use by the client. The code in the
redirect URI is hardcoded.

```shell
http "http://localhost:9000/login/oauth/authorize?response_type=code&client_id=Ov23liuEhYT3CT9Yh6VA&scope=user%3Alogin%2Cname&state=JQOy3kw1PDiQh662ln4DuTGX20ajwb&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Foauth%2Fgithub%2Fcallback"
```

## /login/oauth/access_token
This endpoint takes a token and returns a bearer token. The bearer token is hardcoded. This endpoint requires
application/x-www-form-urlencoded data.

```shell
http --form POST "http://localhost:9000/login/oauth/access_token" grant_type=authorization_code code=SplxlOBeZQQYbYS6WxSbIA
```

## /user
Finally, the user endpoint is called. This returns the user's profile data. The `login` and `name` in the response are
retrieved from Redis and are the values set via the `/system/upcoming-user` endpoint. This endpoint requires an
Authorization header containing `Bearer `, the token itself is not evaluated. Calling this endpoint without setting an
upcoming user in cache results in an HTTP400 Bad Request.

```shell
http "http://localhost:9000/user" "Authorization: Bearer gho_xxx"
```
18 changes: 18 additions & 0 deletions integration_tests/services/github-static-mock/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
services:
cache:
image: redis
restart: always
ports:
- '6379:6379'
profiles:
- test
github:
build: .
ports:
- '9000:9000'
environment:
CACHE_HOST: 'cache'
ELEKTO_HOST: 'elekto'
ELEKTO_PORT: '8000'
profiles:
- test
Loading
Loading