-
Notifications
You must be signed in to change notification settings - Fork 29
Integration tests for Elekto with GitHub oauth mocking #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
c7f6345
69d97f6
1d5e361
8cb8f32
f42dbb2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,22 @@ | ||
| services: | ||
| db: | ||
| image: postgres | ||
| 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 | ||
| 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: | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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' | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These values effectively remain unchanged as long as there is no |
||
| 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' | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||
| 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"] |
| 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 |
| 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`. |
| 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 |
| 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 |
| 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"] |
| 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" | ||
| ``` |
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed due to docker-library/postgres#1259