Skip to content
Merged
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
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ jobs:
- uses: actions/checkout@v2

- name: set up python
uses: actions/setup-python@v1
uses: actions/setup-python@v5

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Outdated test runner

with:
python-version: '3.9.21'
python-version: '3.9'

- name: install dependencies
run: |
Expand Down
4 changes: 2 additions & 2 deletions tcsocket/app/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from asyncio import CancelledError
from time import time

from aiohttp.hdrs import METH_GET, METH_HEAD
from aiohttp.hdrs import METH_GET, METH_HEAD, METH_OPTIONS
from aiohttp.web_exceptions import HTTPBadRequest, HTTPException, HTTPInternalServerError, HTTPMovedPermanently
from aiohttp.web_middlewares import middleware
from aiohttp.web_urldispatcher import SystemRoute
Expand Down Expand Up @@ -174,7 +174,7 @@ async def company_middleware(request, handler):

@middleware
async def json_request_middleware(request, handler):
if request.method not in {METH_GET, METH_HEAD} and request.match_info.route.name:
if request.method not in {METH_GET, METH_HEAD, METH_OPTIONS} and request.match_info.route.name:
error_details = None
try:
data = await request.json()
Expand Down
6 changes: 6 additions & 0 deletions tcsocket/app/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ def pretty_lenient_json(data):

JSON_CONTENT_TYPE = 'application/json'
ACCESS_CONTROL_HEADERS = {'Access-Control-Allow-Origin': '*'}
CORS_PREFLIGHT_HEADERS = {
**ACCESS_CONTROL_HEADERS,
'Access-Control-Allow-Methods': 'POST',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '86400',
}


class HTTPClientErrorJson(web.HTTPClientError):
Expand Down
9 changes: 7 additions & 2 deletions tcsocket/app/views/enquiry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
from operator import itemgetter

import pydantic
from aiohttp.hdrs import METH_POST
from aiohttp.hdrs import METH_OPTIONS, METH_POST
from aiohttp.web_response import Response
from arq.utils import timestamp_ms

from ..geo import get_ip
from ..utils import HTTPBadRequestJson, json_response
from ..utils import CORS_PREFLIGHT_HEADERS, HTTPBadRequestJson, json_response
from ..worker import REDIS_ENQUIRY_CACHE_KEY, get_enquiry_options, store_enquiry_data

logger = logging.getLogger('socket')
Expand All @@ -23,6 +24,10 @@ async def clear_enquiry(request):


async def enquiry(request):
if request.method == METH_OPTIONS:
# CORS preflight sent by browsers before a cross-origin JSON post
return Response(status=204, headers=CORS_PREFLIGHT_HEADERS)

company = dict(request['company'])

redis = request.app['redis']
Expand Down
16 changes: 15 additions & 1 deletion tests/test_enquiries.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ async def test_post_enquiry_success(cli, company, other_server, worker):


async def test_post_enquiry_long_grecaptcha_response(cli, company, other_server, worker):
other_server.app['extra_attributes'] = 'default'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Claude Says: this line made the options define a required attribute (tell-us-about-yourself) that the posted data never included, so the test could only ever get a 400 — it merged green because CI's setup-python step was broken and tests never ran. The test only checks that a long grecaptcha token passes validation, so the attribute setup is dropped.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Alright

data = {
'client_name': 'Cat Flap',
'grecaptcha_response': 'good' * 1000,
Expand Down Expand Up @@ -386,3 +385,18 @@ async def test_post_all_optional(cli, company, other_server):
assert r.status == 201, await r.text()
data = await r.json()
assert data == {'status': 'enquiry submitted to TutorCruncher'}


async def test_options_enquiry_preflight(cli, company):
r = await cli.options(
cli.server.app.router['enquiry'].url_for(company=company.public_key),
headers={
'Origin': 'https://tutorcruncher.com',
'Access-Control-Request-Method': 'POST',
'Access-Control-Request-Headers': 'content-type',
},
)
assert r.status == 204, await r.text()
assert r.headers['Access-Control-Allow-Origin'] == '*'
assert r.headers['Access-Control-Allow-Methods'] == 'POST'
assert r.headers['Access-Control-Allow-Headers'] == 'Content-Type'
Loading