diff --git a/.bumpversion.cfg b/.bumpversion.cfg
index 234774926..ec621723a 100644
--- a/.bumpversion.cfg
+++ b/.bumpversion.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 4.1.20
+current_version = 4.1.21
commit = False
tag = False
@@ -9,10 +9,10 @@ tag = False
[bumpversion:file:src/client/delphi_epidata.R]
+[bumpversion:file:src/client/delphi_epidata.py]
+
[bumpversion:file:src/client/packaging/npm/package.json]
[bumpversion:file:src/client/packaging/pypi/setup.py]
-[bumpversion:file:src/client/packaging/pypi/delphi_epidata/__init__.py]
-
[bumpversion:file:dev/local/setup.cfg]
diff --git a/dev/local/setup.cfg b/dev/local/setup.cfg
index 7295590c8..6cd99cf69 100644
--- a/dev/local/setup.cfg
+++ b/dev/local/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = Delphi Development
-version = 4.1.20
+version = 4.1.21
[options]
packages =
@@ -24,6 +24,7 @@ packages =
delphi.epidata.acquisition.twtr
delphi.epidata.acquisition.wiki
delphi.epidata.client
+ delphi.epidata.common
delphi.epidata.server
delphi.epidata.server.admin
delphi.epidata.server.admin.templates
diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock
index 8ecc2115b..af94c77e4 100644
--- a/docs/Gemfile.lock
+++ b/docs/Gemfile.lock
@@ -205,14 +205,14 @@ GEM
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mercenary (0.3.6)
- mini_portile2 (2.8.5)
+ mini_portile2 (2.8.6)
minima (2.5.1)
jekyll (>= 3.5, < 5.0)
jekyll-feed (~> 0.9)
jekyll-seo-tag (~> 2.1)
minitest (5.17.0)
multipart-post (2.1.1)
- nokogiri (1.16.2)
+ nokogiri (1.16.5)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
octokit (4.20.0)
@@ -225,7 +225,8 @@ GEM
rb-fsevent (0.10.4)
rb-inotify (0.10.1)
ffi (~> 1.0)
- rexml (3.2.5)
+ rexml (3.2.8)
+ strscan (>= 3.0.9)
rouge (3.26.0)
ruby-enum (0.9.0)
i18n
@@ -242,6 +243,7 @@ GEM
faraday (> 0.8, < 2.0)
simpleidn (0.2.1)
unf (~> 0.1.4)
+ strscan (3.1.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thread_safe (0.3.6)
diff --git a/docs/api/covidcast-signals/jhu-csse.md b/docs/api/covidcast-signals/jhu-csse.md
index 99cd6d707..44955430c 100644
--- a/docs/api/covidcast-signals/jhu-csse.md
+++ b/docs/api/covidcast-signals/jhu-csse.md
@@ -1,6 +1,6 @@
---
title: JHU Cases and Deaths
-parent: Data Sources and Signals
+parent: Inactive Signals
grand_parent: COVIDcast Main Endpoint
---
@@ -23,12 +23,12 @@ University.
| Signal and 7-day average signal | Description |
|---|---|
-| `confirmed_cumulative_num` | Cumulative number of confirmed COVID-19 cases
**Earliest date available:** 2020-01-22 & 2020-02-20 |
-| `confirmed_cumulative_prop` | Cumulative number of confirmed COVID-19 cases per 100,000 population
**Earliest date available:** 2020-01-22 & 2020-02-20 |
+| `confirmed_cumulative_num` and `confirmed_7dav_cumulative_num` | Cumulative number of confirmed COVID-19 cases
**Earliest date available:** 2020-01-22 & 2020-02-20 |
+| `confirmed_cumulative_prop` and `confirmed_7dav_cumulative_prop` | Cumulative number of confirmed COVID-19 cases per 100,000 population
**Earliest date available:** 2020-01-22 & 2020-02-20 |
| `confirmed_incidence_num` and `confirmed_7dav_incidence_num` | Number of new confirmed COVID-19 cases, daily
**Earliest date available:** 2020-01-22 & 2020-02-20 |
| `confirmed_incidence_prop` and `confirmed_7dav_incidence_prop` | Number of new confirmed COVID-19 cases per 100,000 population, daily
**Earliest date available:** 2020-01-22 & 2020-02-20 |
-| `deaths_cumulative_num` | Cumulative number of confirmed deaths due to COVID-19
**Earliest date available:** 2020-01-22 & 2020-02-20 |
-| `deaths_cumulative_prop` | Cumulative number of confirmed due to COVID-19, per 100,000 population
**Earliest date available:** 2020-01-22 & 2020-02-20 |
+| `deaths_cumulative_num` and `deaths_7dav_cumulative_num` | Cumulative number of confirmed deaths due to COVID-19
**Earliest date available:** 2020-01-22 & 2020-02-20 |
+| `deaths_cumulative_prop` and `deaths_7dav_cumulative_prop` | Cumulative number of confirmed due to COVID-19, per 100,000 population
**Earliest date available:** 2020-01-22 & 2020-02-20 |
| `deaths_incidence_num` and `deaths_7dav_incidence_num` | Number of new confirmed deaths due to COVID-19, daily
**Earliest date available:** 2020-01-22 & 2020-02-20 |
| `deaths_incidence_prop` and `deaths_7dav_incidence_prop` | Number of new confirmed deaths due to COVID-19 per 100,000 population, daily
**Earliest date available:** 2020-01-22 & 2020-02-20 |
diff --git a/docs/api/covidcast-signals/nchs-mortality.md b/docs/api/covidcast-signals/nchs-mortality.md
index 1a51a7bcf..9e46aa4d4 100644
--- a/docs/api/covidcast-signals/nchs-mortality.md
+++ b/docs/api/covidcast-signals/nchs-mortality.md
@@ -68,7 +68,7 @@ York State in our reports.
We report the NCHS Mortality data in a weekly format (`time_type=week` &
`time_value={YYYYWW}`, where `YYYYWW` refers to an epiweek). The CDC defines
-the [epiweek](https://wwwn.cdc.gov/nndss/document/MMWR_Week_overview.pdf) as
+the [epiweek](https://web.archive.org/web/20210623224758/https://wwwn.cdc.gov/nndss/document/MMWR_Week_overview.pdf) as
seven days, from Sunday to Saturday. We check the week-ending dates provided in
the NCHS morality data and use Python package
[epiweeks](https://pypi.org/project/epiweeks/) to convert them into epiweek
diff --git a/docs/symptom-survey/publications.md b/docs/symptom-survey/publications.md
index 8b0eb768e..8571cbe3d 100644
--- a/docs/symptom-survey/publications.md
+++ b/docs/symptom-survey/publications.md
@@ -26,6 +26,23 @@ Pandemic"](https://www.pnas.org/topic/548) in *PNAS*:
Research publications using the survey data include:
+- Z. Yang, R. Krishnan, and B. Li (2024). [The interplay between individual
+ mobility, health risk, and economic choice: A holistic model for COVID-19
+ policy intervention](https://doi.org/10.1287/ijds.2023.0013). *INFORMS
+ Journal on Data Science*.
+- A. Srivastava, J. M. Ramirez, S. Díaz-Aranda, J. Aguilar, A. F. Anta, A. Ortega,
+ and R. E. Lillo (2024). [Nowcasting temporal trends using indirect surveys](https://doi.org/10.1609/aaai.v38i20.30242).
+ In *Proceedings of the 38th AAAI Conference on Artificial Intelligence* 38,
+ 22359–22367.
+- P. Porebski, S. Venkatramanan, A. Adiga, B. Klahn, B. Hurt, M. L. Wilson,
+ J. Chen, A. Vullikanti, M. Marathe & B. Lewis (2024). [Data-driven
+ mechanistic framework with stratified immunity and effective transmissibility
+ for COVID-19 scenario projections](https://doi.org/10.1016/j.epidem.2024.100761).
+ *Epidemics* 100761.
+- V. Nelson, B. Bashyal, P.-N. Tan & Y. A. Argyris (2024). [Vaccine rhetoric
+ on social media and COVID-19 vaccine uptake rates: A triangulation using
+ self-reported vaccine acceptance](https://doi.org/10.1016/j.socscimed.2024.116775).
+ *Social Science & Medicine* 116775.
- R.R. Andridge (2024). [Using proxy pattern-mixture models to explain bias in
estimates of COVID-19 vaccine uptake from two large surveys](https://doi.org/10.1093/jrsssa/qnae005).
*Journal of the Royal Statistical Society Series A: Statistics in Society*.
@@ -34,7 +51,7 @@ Research publications using the survey data include:
*IISE Transactions*.
- de Vries, M., Kim, J.Y. & Han, H. (2023). [The unequal landscape of civic
opportunity in America](https://doi.org/10.1038/s41562-023-01743-1). *Nature
- Human Behavior*.
+ Human Behavior* 8, 256-263.
- E. Tuzhilina, T. J. Hastie, D. J. McDonald, J. K. Tay & R. Tibshirani (2023).
[Smooth multi-period forecasting with application to prediction of COVID-19
cases](https://doi.org/10.1080/10618600.2023.2285337). *Journal of Computational
diff --git a/integrations/client/test_delphi_epidata.py b/integrations/client/test_delphi_epidata.py
index 2af923b2b..02a1a9275 100644
--- a/integrations/client/test_delphi_epidata.py
+++ b/integrations/client/test_delphi_epidata.py
@@ -3,6 +3,7 @@
# standard library
import time
from json import JSONDecodeError
+from requests.models import Response
from unittest.mock import MagicMock, patch
# first party
@@ -41,6 +42,8 @@ def localSetUp(self):
# use the local instance of the Epidata API
Epidata.BASE_URL = 'http://delphi_web_epidata/epidata'
Epidata.auth = ('epidata', 'key')
+ Epidata.debug = False
+ Epidata.sandbox = False
# use the local instance of the epidata database
secrets.db.host = 'delphi_database_epidata'
@@ -221,6 +224,82 @@ def test_retry_request(self, get):
{'result': 0, 'message': 'error: Expecting value: line 1 column 1 (char 0)'}
)
+ @patch('requests.post')
+ @patch('requests.get')
+ def test_debug(self, get, post):
+ """Test that in debug mode request params are correctly logged."""
+ class MockResponse:
+ def __init__(self, content, status_code):
+ self.content = content
+ self.status_code = status_code
+ def raise_for_status(self): pass
+
+ Epidata.debug = True
+
+ try:
+ with self.subTest(name='test multiple GET'):
+ with self.assertLogs('delphi_epidata_client', level='INFO') as logs:
+ get.reset_mock()
+ get.return_value = MockResponse(b'{"key": "value"}', 200)
+ Epidata._request_with_retry("test_endpoint1", params={"key1": "value1"})
+ Epidata._request_with_retry("test_endpoint2", params={"key2": "value2"})
+
+ output = logs.output
+ self.assertEqual(len(output), 4) # [request, response, request, response]
+ self.assertIn("Sending GET request", output[0])
+ self.assertIn("\"url\": \"http://delphi_web_epidata/epidata/test_endpoint1/\"", output[0])
+ self.assertIn("\"params\": {\"key1\": \"value1\"}", output[0])
+ self.assertIn("Received response", output[1])
+ self.assertIn("\"status_code\": 200", output[1])
+ self.assertIn("\"len\": 16", output[1])
+ self.assertIn("Sending GET request", output[2])
+ self.assertIn("\"url\": \"http://delphi_web_epidata/epidata/test_endpoint2/\"", output[2])
+ self.assertIn("\"params\": {\"key2\": \"value2\"}", output[2])
+ self.assertIn("Received response", output[3])
+ self.assertIn("\"status_code\": 200", output[3])
+ self.assertIn("\"len\": 16", output[3])
+
+ with self.subTest(name='test GET and POST'):
+ with self.assertLogs('delphi_epidata_client', level='INFO') as logs:
+ get.reset_mock()
+ get.return_value = MockResponse(b'{"key": "value"}', 414)
+ post.reset_mock()
+ post.return_value = MockResponse(b'{"key": "value"}', 200)
+ Epidata._request_with_retry("test_endpoint3", params={"key3": "value3"})
+
+ output = logs.output
+ self.assertEqual(len(output), 3) # [request, response, request, response]
+ self.assertIn("Sending GET request", output[0])
+ self.assertIn("\"url\": \"http://delphi_web_epidata/epidata/test_endpoint3/\"", output[0])
+ self.assertIn("\"params\": {\"key3\": \"value3\"}", output[0])
+ self.assertIn("Received 414 response, retrying as POST request", output[1])
+ self.assertIn("\"url\": \"http://delphi_web_epidata/epidata/test_endpoint3/\"", output[1])
+ self.assertIn("\"params\": {\"key3\": \"value3\"}", output[1])
+ self.assertIn("Received response", output[2])
+ self.assertIn("\"status_code\": 200", output[2])
+ self.assertIn("\"len\": 16", output[2])
+ finally: # make sure this global is always reset
+ Epidata.debug = False
+
+ @patch('requests.post')
+ @patch('requests.get')
+ def test_sandbox(self, get, post):
+ """Test that in debug + sandbox mode request params are correctly logged, but no queries are sent."""
+ Epidata.debug = True
+ Epidata.sandbox = True
+ try:
+ with self.assertLogs('delphi_epidata_client', level='INFO') as logs:
+ Epidata.covidcast('src', 'sig', 'day', 'county', 20200414, '01234')
+ output = logs.output
+ self.assertEqual(len(output), 1)
+ self.assertIn("Sending GET request", output[0])
+ self.assertIn("\"url\": \"http://delphi_web_epidata/epidata/covidcast/\"", output[0])
+ get.assert_not_called()
+ post.assert_not_called()
+ finally: # make sure these globals are always reset
+ Epidata.debug = False
+ Epidata.sandbox = False
+
def test_geo_value(self):
"""test different variants of geo types: single, *, multi."""
diff --git a/requirements.api.txt b/requirements.api.txt
index dfaedc0ff..5a04355f9 100644
--- a/requirements.api.txt
+++ b/requirements.api.txt
@@ -2,7 +2,7 @@ delphi_utils==0.3.15
epiweeks==2.1.2
Flask==2.2.5
Flask-Limiter==3.3.0
-jinja2==3.1.3
+jinja2==3.1.4
more_itertools==8.4.0
mysqlclient==2.1.1
orjson==3.9.15
diff --git a/requirements.dev.txt b/requirements.dev.txt
index 818a21553..051a61fdf 100644
--- a/requirements.dev.txt
+++ b/requirements.dev.txt
@@ -1,4 +1,4 @@
-aiohttp==3.9.2
+aiohttp==3.9.4
black>=20.8b1
bump2version==1.0.1
covidcast==0.1.5
diff --git a/src/client/delphi_epidata.R b/src/client/delphi_epidata.R
index e4024b204..268f03a2f 100644
--- a/src/client/delphi_epidata.R
+++ b/src/client/delphi_epidata.R
@@ -15,7 +15,7 @@ Epidata <- (function() {
# API base url
BASE_URL <- getOption('epidata.url', default = 'https://api.delphi.cmu.edu/epidata/')
- client_version <- '4.1.20'
+ client_version <- '4.1.21'
auth <- getOption("epidata.auth", default = NA)
diff --git a/src/client/delphi_epidata.js b/src/client/delphi_epidata.js
index 7c684fb78..e53828b15 100644
--- a/src/client/delphi_epidata.js
+++ b/src/client/delphi_epidata.js
@@ -22,7 +22,7 @@
}
})(this, function (exports, fetchImpl, jQuery) {
const BASE_URL = "https://api.delphi.cmu.edu/epidata/";
- const client_version = "4.1.20";
+ const client_version = "4.1.21";
// Helper function to cast values and/or ranges to strings
function _listitem(value) {
diff --git a/src/client/delphi_epidata.py b/src/client/delphi_epidata.py
index 22fd4d4e2..6863e4261 100644
--- a/src/client/delphi_epidata.py
+++ b/src/client/delphi_epidata.py
@@ -10,21 +10,17 @@
# External modules
import requests
+import time
import asyncio
from tenacity import retry, stop_after_attempt
from aiohttp import ClientSession, TCPConnector, BasicAuth
-from importlib.metadata import version, PackageNotFoundError
-# Obtain package version for the user-agent. Uses the installed version by
-# preference, even if you've installed it and then use this script independently
-# by accident.
-try:
- _version = version("delphi-epidata")
-except PackageNotFoundError:
- _version = "0.script"
+from delphi.epidata.common.logger import get_structured_logger
-_HEADERS = {"user-agent": "delphi_epidata/" + _version + " (Python)"}
+__version__ = "4.1.21"
+
+_HEADERS = {"user-agent": "delphi_epidata/" + __version__ + " (Python)"}
class EpidataException(Exception):
@@ -47,7 +43,11 @@ class Epidata:
BASE_URL = "https://api.delphi.cmu.edu/epidata"
auth = None
- client_version = _version
+ client_version = __version__
+
+ logger = get_structured_logger('delphi_epidata_client')
+ debug = False # if True, prints extra logging statements
+ sandbox = False # if True, will not execute any queries
# Helper function to cast values and/or ranges to strings
@staticmethod
@@ -71,9 +71,25 @@ def _list(values):
def _request_with_retry(endpoint, params={}):
"""Make request with a retry if an exception is thrown."""
request_url = f"{Epidata.BASE_URL}/{endpoint}/"
+ if Epidata.debug:
+ Epidata.logger.info("Sending GET request", url=request_url, params=params, headers=_HEADERS, auth=Epidata.auth)
+ if Epidata.sandbox:
+ resp = requests.Response()
+ resp._content = b'true'
+ return resp
+ start_time = time.time()
req = requests.get(request_url, params, auth=Epidata.auth, headers=_HEADERS)
if req.status_code == 414:
+ if Epidata.debug:
+ Epidata.logger.info("Received 414 response, retrying as POST request", url=request_url, params=params, headers=_HEADERS)
req = requests.post(request_url, params, auth=Epidata.auth, headers=_HEADERS)
+ if Epidata.debug:
+ Epidata.logger.info(
+ "Received response",
+ status_code=req.status_code,
+ len=len(req.content),
+ time=round(time.time() - start_time, 4)
+ )
# handle 401 and 429
req.raise_for_status()
return req
diff --git a/src/client/packaging/npm/package.json b/src/client/packaging/npm/package.json
index 460042303..95bb6fdf2 100644
--- a/src/client/packaging/npm/package.json
+++ b/src/client/packaging/npm/package.json
@@ -2,7 +2,7 @@
"name": "delphi_epidata",
"description": "Delphi Epidata API Client",
"authors": "Delphi Group",
- "version": "4.1.20",
+ "version": "4.1.21",
"license": "MIT",
"homepage": "https://github.com/cmu-delphi/delphi-epidata",
"bugs": {
diff --git a/src/client/packaging/pypi/CHANGELOG.md b/src/client/packaging/pypi/CHANGELOG.md
index ee95128f7..7cefbf96c 100644
--- a/src/client/packaging/pypi/CHANGELOG.md
+++ b/src/client/packaging/pypi/CHANGELOG.md
@@ -3,6 +3,38 @@
All notable future changes to the `delphi_epidata` python client will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/).
+## [4.1.21] - 2024-05-20
+
+### Includes
+- https://github.com/cmu-delphi/delphi-epidata/pull/1418
+- https://github.com/cmu-delphi/delphi-epidata/pull/1436
+
+### Added
+- Adds two debug flags:
+ - `debug` logs info about HTTP requests and responses
+ - `sandbox` prevents any HTTP requests from actually executing, allowing for tests that do not incur server load.
+- Fixes the `user-agent` version so that it is correctly set to match the current client release.
+
+## [4.1.17] - 2024-01-30
+
+### Includes
+- https://github.com/cmu-delphi/delphi-epidata/pull/1363
+
+### Changed
+- Replaced use of deprecated setuptools' `pkg_resources` library with the native `importlib.metadata` library.
+
+## [4.1.13] - 2023-11-04
+
+### Includes
+- https://github.com/cmu-delphi/delphi-epidata/pull/1323
+- https://github.com/cmu-delphi/delphi-epidata/pull/1330
+
+### Changed
+- Appends a trailing slash to URLs requested by the Python client, which should prevent an automatic redirect and an extra request to the server.
+
+### Removed
+- Removed the `covidcast_nowcast()` method, as the associated API endpoint is no longer available.
+
## [4.1.11] - 2023-10-12
### Includes
diff --git a/src/client/packaging/pypi/delphi_epidata/__init__.py b/src/client/packaging/pypi/delphi_epidata/__init__.py
index c50efd740..2c92252a6 100644
--- a/src/client/packaging/pypi/delphi_epidata/__init__.py
+++ b/src/client/packaging/pypi/delphi_epidata/__init__.py
@@ -1,4 +1,3 @@
-from .delphi_epidata import Epidata
+from .delphi_epidata import Epidata, __version__
name = "delphi_epidata"
-__version__ = "4.1.20"
diff --git a/src/client/packaging/pypi/setup.py b/src/client/packaging/pypi/setup.py
index 7c0ee051e..c274b466f 100644
--- a/src/client/packaging/pypi/setup.py
+++ b/src/client/packaging/pypi/setup.py
@@ -5,7 +5,7 @@
setuptools.setup(
name="delphi_epidata",
- version="4.1.20",
+ version="4.1.21",
author="David Farrow",
author_email="dfarrow0@gmail.com",
description="A programmatic interface to Delphi's Epidata API.",
@@ -13,7 +13,7 @@
long_description_content_type="text/markdown",
url="https://github.com/cmu-delphi/delphi-epidata",
project_urls={
- "Changelog": "https://github.com/cmu-delphi/delphi-epidata/blob/dev/src/client/packaging/pypi/CHANGELOG.md",
+ "Changelog": "https://github.com/cmu-delphi/delphi-epidata/blob/main/src/client/packaging/pypi/CHANGELOG.md",
},
packages=setuptools.find_packages(),
install_requires=["aiohttp", "requests>=2.7.0", "tenacity"],
diff --git a/src/server/_common.py b/src/server/_common.py
index 8633d07fd..33a3f9c48 100644
--- a/src/server/_common.py
+++ b/src/server/_common.py
@@ -68,6 +68,7 @@ def log_info_with_request(message, **kwargs):
remote_addr=request.remote_addr,
real_remote_addr=get_real_ip_addr(request),
user_agent=request.user_agent.string,
+ referrer=request.referrer or request.origin,
api_key=resolve_auth_token(),
user_id=(current_user and current_user.id),
**kwargs
@@ -114,19 +115,7 @@ def before_request_execute():
user = current_user
api_key = resolve_auth_token()
- # TODO: replace this next call with: log_info_with_request("Received API request")
- get_structured_logger("server_api").info(
- "Received API request",
- method=request.method,
- url=request.url,
- form_args=request.form,
- req_length=request.content_length,
- remote_addr=request.remote_addr,
- real_remote_addr=get_real_ip_addr(request),
- user_agent=request.user_agent.string,
- api_key=api_key,
- user_id=(user and user.id)
- )
+ log_info_with_request("Received API request")
if not _is_public_route() and api_key and not user:
# if this is a privleged endpoint, and an api key was given but it does not look up to a user, raise exception:
@@ -150,28 +139,10 @@ def after_request_execute(response):
# Convert to milliseconds
total_time *= 1000
- api_key = resolve_auth_token()
-
update_key_last_time_used(current_user)
- # TODO: replace this next call with: log_info_with_request_and_response("Served API request", response, elapsed_time_ms=total_time)
- get_structured_logger("server_api").info(
- "Served API request",
- method=request.method,
- url=request.url,
- form_args=request.form,
- req_length=request.content_length,
- remote_addr=request.remote_addr,
- real_remote_addr=get_real_ip_addr(request),
- user_agent=request.user_agent.string,
- api_key=api_key,
- values=request.values.to_dict(flat=False),
- blueprint=request.blueprint,
- endpoint=request.endpoint,
- response_status=response.status,
- content_length=response.calculate_content_length(),
- elapsed_time_ms=total_time,
- )
+ log_info_with_request_and_response("Served API request", response, elapsed_time_ms=total_time)
+
return response
diff --git a/src/server/_config.py b/src/server/_config.py
index c48de0908..8d50ba199 100644
--- a/src/server/_config.py
+++ b/src/server/_config.py
@@ -7,7 +7,7 @@
load_dotenv()
-VERSION = "4.1.20"
+VERSION = "4.1.21"
MAX_RESULTS = int(10e6)
MAX_COMPATIBILITY_RESULTS = int(3650)
diff --git a/tests/server/test_validate.py b/tests/server/test_validate.py
index f06e9e997..eff7e9c9e 100644
--- a/tests/server/test_validate.py
+++ b/tests/server/test_validate.py
@@ -26,6 +26,7 @@ def setUp(self):
app.config["TESTING"] = True
app.config["WTF_CSRF_ENABLED"] = False
app.config["DEBUG"] = False
+ self.client = app.test_client()
def test_require_all(self):
with self.subTest("all given"):
@@ -60,3 +61,39 @@ def test_require_any(self):
with self.subTest("one options given with is empty but ok"):
with app.test_request_context("/?abc="):
self.assertTrue(require_any(request, "abc", empty=True))
+
+ def test_origin_headers(self):
+ with self.subTest("referer only"):
+ with self.assertLogs("server_api", level='INFO') as logs:
+ self.client.get("/signal_dashboard_status", headers={
+ "Referer": "https://test.com/test"
+ })
+ output = logs.output
+ self.assertEqual(len(output), 2) # [before_request, after_request]
+ self.assertIn("Received API request", output[0])
+ self.assertIn("\"referrer\": \"https://test.com/test\"", output[0])
+ self.assertIn("Served API request", output[1])
+ self.assertIn("\"referrer\": \"https://test.com/test\"", output[1])
+ with self.subTest("origin only"):
+ with self.assertLogs("server_api", level='INFO') as logs:
+ self.client.get("/signal_dashboard_status", headers={
+ "Origin": "https://test.com"
+ })
+ output = logs.output
+ self.assertEqual(len(output), 2) # [before_request, after_request]
+ self.assertIn("Received API request", output[0])
+ self.assertIn("\"referrer\": \"https://test.com\"", output[0])
+ self.assertIn("Served API request", output[1])
+ self.assertIn("\"referrer\": \"https://test.com\"", output[1])
+ with self.subTest("referer overrides origin"):
+ with self.assertLogs("server_api", level='INFO') as logs:
+ self.client.get("/signal_dashboard_status", headers={
+ "Referer": "https://test.com/test",
+ "Origin": "https://test.com"
+ })
+ output = logs.output
+ self.assertEqual(len(output), 2) # [before_request, after_request]
+ self.assertIn("Received API request", output[0])
+ self.assertIn("\"referrer\": \"https://test.com/test\"", output[0])
+ self.assertIn("Served API request", output[1])
+ self.assertIn("\"referrer\": \"https://test.com/test\"", output[1])