1
0
mirror of https://github.com/ilri/dspace-statistics-api.git synced 2025-05-23 12:32:46 +02:00

Compare commits

..

21 Commits

Author SHA1 Message Date
13736d6359 CHANGELOG.md: Move unreleased changes to verison 0.8.0 2018-11-11 17:16:26 +02:00
4fc64edeb8 Merge pull request #6 from ilri/pytest
Pytest
2018-11-11 17:14:49 +02:00
2a8901dc4f CHANGELOG.md: Update notes 2018-11-11 17:10:45 +02:00
e25c974796 README.md: We have tests now 2018-11-11 17:08:51 +02:00
ffc62e9ee6 tests/test_api.py: Use response.text for all json.loads()
This allows the code to work in Python 3.5 as well as 3.6+.
2018-11-11 17:05:31 +02:00
556c5ae088 tests/test_api.py: Use response.text instead of content
Falcon's response content is raw bytes, while its text is a string.
Let's use the latter so we can use json.loads() in Python 3.5, 3.6,
and 3.7 with the same code.

See: https://falcon.readthedocs.io/en/stable/api/testing.html
2018-11-11 17:01:17 +02:00
d94134f80a tests/test_api.py: Try to add workaround for Python 3.5
In Python 3.5 it seems that json.loads() cannot decode a bytes, but
it works in Python 3.6 and 3.7. Let's try a workaround to see if we
can get it working on both Python 3.5 and 3.6+.

See: https://docs.python.org/3.5/library/json.html#json.loads
See: https://docs.python.org/3.6/library/json.html#json.loads
2018-11-11 17:00:20 +02:00
586231eb2d .travis.yml: Use PostgreSQL 9.5
Default PostgreSQL in Travis CI is 9.2 which is very old, so let's
try to use 9.5.

See: https://docs.travis-ci.com/user/database-setup/#postgresql
2018-11-11 16:41:35 +02:00
766b77a3b6 .travis.yml: Use PostgreSQL directly
It seems that Travis CI already has a PostgreSQL service running.
2018-11-11 16:35:28 +02:00
1959e8154e .travis.yml: Use localhost for Docker's PostgreSQL ports
See: https://docs.travis-ci.com/user/docker/
2018-11-11 16:28:18 +02:00
d40b2f0b2e Test API using pytest and PostgreSQL on Travis
First attempt at getting the Travis Docker setup correct. Inspired
by the Travis pipenv setup used in Responder.

See: https://docs.travis-ci.com/user/docker/
See: https://github.com/kennethreitz/responder/blob/master/.travis.yml
2018-11-11 16:25:16 +02:00
061d0a8f5f CHANGELOG.md: Add API tests to unreleased changes 2018-11-11 16:24:54 +02:00
e57660ff88 Add initial pytest configuration
From: https://github.com/kennethreitz/responder/blob/master/pytest.ini
2018-11-11 16:24:54 +02:00
5c8756bede Add pytest to pipenv development packages 2018-11-11 16:24:54 +02:00
bae9fb80e4 Add initial API tests
Test the basic assumptions of the API like response codes and types.
2018-11-11 16:24:54 +02:00
8a65d99e08 .travis.yml: Don't limit builds to master
This is good in theory but it means we can't trigger builds for other
branches on the fly from the Travis web interface.
2018-11-11 16:21:48 +02:00
d479b7dc6c CHANGELOG.md: Syntax fixes 2018-11-11 00:08:44 +02:00
40aac8bf89 Merge pull request #5 from ilri/database-error-handling
Database error handling
2018-11-11 00:07:25 +02:00
53ba6f2936 CHANGELOG.md: Add database try/except to unreleased changes 2018-11-11 00:05:49 +02:00
140cc4cb07 README.md: Remove TODO for database try/except
Now database connection errors are properly excepted and raised.
2018-11-11 00:04:28 +02:00
d5d2d2149b dspace_statistics_api/database.py: Raise HTTP 500 on error
Properly except on database connection error and raise an HTTP 500
instead of spamming the console/log with twenty lines of text.
2018-11-10 23:58:58 +02:00
10 changed files with 77393 additions and 27 deletions

View File

@ -3,9 +3,17 @@ python:
- "3.5" - "3.5"
- "3.6" - "3.6"
- "3.7-dev" - "3.7-dev"
script: pip install -r requirements.txt addons:
branches: postgresql: "9.5"
only: before_script:
- master - psql --version
- createuser -U postgres dspacestatistics
- psql -U postgres -c "ALTER USER dspacestatistics WITH PASSWORD 'dspacestatistics'"
- createdb -U postgres -O dspacestatistics --encoding=UNICODE dspacestatistics
- psql -U postgres -d dspacestatistics < tests/dspacestatistics.sql
install:
- "pip install pipenv --upgrade-strategy=only-if-needed"
- "pipenv install --dev"
script: pytest
# vim: ts=2 sw=2 et # vim: ts=2 sw=2 et

View File

@ -4,46 +4,53 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### [0.7.0] - 2018-11-07 ## [0.8.0] - 2018-11-11
## Added ### Changed
- Properly handle database connection errors
### Added
- API tests with pytest
## [0.7.0] - 2018-11-07
### Added
- Ability to configure PostgreSQL database port with DATABASE_PORT environment variable (defaults to 5432) - Ability to configure PostgreSQL database port with DATABASE_PORT environment variable (defaults to 5432)
- Hound CI configuration to validate pull requests against PEP 8 code style with Flake8 - Hound CI configuration to validate pull requests against PEP 8 code style with Flake8
- Configuration for [pipenv](https://pipenv.readthedocs.io/en/latest/) - Configuration for [pipenv](https://pipenv.readthedocs.io/en/latest/)
## Changed ### Changed
- Use a database management class with Python context management to automatically open/close connections and cursors - Use a database management class with Python context management to automatically open/close connections and cursors
## Changed ### Changed
- Validate code against PEP 8 style guide with Flake8 - Validate code against PEP 8 style guide with Flake8
### [0.6.1] - 2018-10-31 ## [0.6.1] - 2018-10-31
## Added ### Added
- API documentation at root path (/) - API documentation at root path (/)
### [0.6.0] - 2018-10-31 ## [0.6.0] - 2018-10-31
## Changed ### Changed
- Refactor project structure (note breaking changes to API and indexing invocation, see contrib and README.md) - Refactor project structure (note breaking changes to API and indexing invocation, see contrib and README.md)
### [0.5.2] - 2018-10-28 ## [0.5.2] - 2018-10-28
## Changed ### Changed
- Update library versions in requirements.txt - Update library versions in requirements.txt
### [0.5.1] - 2018-10-24 ## [0.5.1] - 2018-10-24
## Changed ### Changed
- Use Python's native json instead of ujson - Use Python's native json instead of ujson
### [0.5.0] - 2018-10-24 ## [0.5.0] - 2018-10-24
## Added ### Added
- Example nginx configuration to README.md - Example nginx configuration to README.md
## Changed ### Changed
- Don't initialize Solr connection in API - Don't initialize Solr connection in API
### [0.4.3] - 2018-10-17 ## [0.4.3] - 2018-10-17
## Changed ### Changed
- Use pip install as script for Travis CI - Use pip install as script for Travis CI
## Improved ### Improved
- Documentation for deployment and testing - Documentation for deployment and testing
## [0.4.2] - 2018-10-04 ## [0.4.2] - 2018-10-04
@ -57,7 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [0.4.1] - 2018-09-26 ## [0.4.1] - 2018-09-26
### Changed ### Changed
- Use execute_values() to batch insert records to PostgreSQL - Use `execute_values()` to batch insert records to PostgreSQL
## [0.4.0] - 2018-09-25 ## [0.4.0] - 2018-09-25
### Fixed ### Fixed

View File

@ -20,6 +20,7 @@ solrclient = {ref = "kazoo-2.5.0", git = "https://github.com/alanorth/SolrClient
[dev-packages] [dev-packages]
"flake8" = "*" "flake8" = "*"
ipython = "*" ipython = "*"
pytest = "*"
[requires] [requires]
python_version = "3.6" python_version = "3.6"

46
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "74430260b3271348f65792cc7f9cadc5d2036abc4a5fc958524239656ffabb4f" "sha256": "da754a9f6c668303b071155fdd3a31067897f2c1703a28990745506a5ea55538"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -138,6 +138,20 @@
} }
}, },
"develop": { "develop": {
"atomicwrites": {
"hashes": [
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
],
"version": "==1.2.1"
},
"attrs": {
"hashes": [
"sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
"sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
],
"version": "==18.2.0"
},
"backcall": { "backcall": {
"hashes": [ "hashes": [
"sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4", "sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4",
@ -189,6 +203,14 @@
], ],
"version": "==0.6.1" "version": "==0.6.1"
}, },
"more-itertools": {
"hashes": [
"sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
"sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
"sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
],
"version": "==4.3.0"
},
"parso": { "parso": {
"hashes": [ "hashes": [
"sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2", "sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2",
@ -211,6 +233,13 @@
], ],
"version": "==0.7.5" "version": "==0.7.5"
}, },
"pluggy": {
"hashes": [
"sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095",
"sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f"
],
"version": "==0.8.0"
},
"prompt-toolkit": { "prompt-toolkit": {
"hashes": [ "hashes": [
"sha256:c1d6aff5252ab2ef391c2fe498ed8c088066f66bc64a8d5c095bbf795d9fec34", "sha256:c1d6aff5252ab2ef391c2fe498ed8c088066f66bc64a8d5c095bbf795d9fec34",
@ -226,6 +255,13 @@
], ],
"version": "==0.6.0" "version": "==0.6.0"
}, },
"py": {
"hashes": [
"sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694",
"sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"
],
"version": "==1.7.0"
},
"pycodestyle": { "pycodestyle": {
"hashes": [ "hashes": [
"sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83", "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83",
@ -247,6 +283,14 @@
], ],
"version": "==2.2.0" "version": "==2.2.0"
}, },
"pytest": {
"hashes": [
"sha256:630ff1dbe04f469ee78faa5660f712e58b953da7df22ea5d828c9012e134da43",
"sha256:a2b5232735dd0b736cbea9c0f09e5070d78fcaba2823a4f6f09d9a81bd19415c"
],
"index": "pypi",
"version": "==3.10.0"
},
"six": { "six": {
"hashes": [ "hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",

View File

@ -35,6 +35,10 @@ Test to see if there are any statistics:
$ curl 'http://localhost:8000/items?limit=1' $ curl 'http://localhost:8000/items?limit=1'
Run tests:
$ pytest
## Deployment ## Deployment
There are example systemd service and timer units in the `contrib` directory. The API service listens on localhost by default so you will need to expose it publicly using a web server like nginx. There are example systemd service and timer units in the `contrib` directory. The API service listens on localhost by default so you will need to expose it publicly using a web server like nginx.
@ -71,8 +75,6 @@ The item id is the *internal* id for an item. You can get these from the standar
## Todo ## Todo
- Better logging - Better logging
- Tests
- Check if database exists (try/except)
- Version API - Version API
- Use JSON in PostgreSQL - Use JSON in PostgreSQL
- Switch to [Python 3.6+ f-string syntax](https://realpython.com/python-f-strings/) - Switch to [Python 3.6+ f-string syntax](https://realpython.com/python-f-strings/)

View File

@ -3,6 +3,7 @@ from .config import DATABASE_USER
from .config import DATABASE_PASS from .config import DATABASE_PASS
from .config import DATABASE_HOST from .config import DATABASE_HOST
from .config import DATABASE_PORT from .config import DATABASE_PORT
import falcon
import psycopg2 import psycopg2
import psycopg2.extras import psycopg2.extras
@ -14,7 +15,13 @@ class DatabaseManager():
self._connection_uri = 'dbname={} user={} password={} host={} port={}'.format(DATABASE_NAME, DATABASE_USER, DATABASE_PASS, DATABASE_HOST, DATABASE_PORT) self._connection_uri = 'dbname={} user={} password={} host={} port={}'.format(DATABASE_NAME, DATABASE_USER, DATABASE_PASS, DATABASE_HOST, DATABASE_PORT)
def __enter__(self): def __enter__(self):
self._connection = psycopg2.connect(self._connection_uri, cursor_factory=psycopg2.extras.DictCursor) try:
self._connection = psycopg2.connect(self._connection_uri, cursor_factory=psycopg2.extras.DictCursor)
except psycopg2.OperationalError:
title = '500 Internal Server Error'
description = 'Could not connect to database'
raise falcon.HTTPInternalServerError(title, description)
return self._connection return self._connection
def __exit__(self, exc_type, exc_value, exc_traceback): def __exit__(self, exc_type, exc_value, exc_traceback):

4
pytest.ini Normal file
View File

@ -0,0 +1,4 @@
[pytest]
addopts= -rsxX -s -v --strict
filterwarnings =
error::UserWarning

0
tests/__init__.py Normal file
View File

77226
tests/dspacestatistics.sql Normal file

File diff suppressed because it is too large Load Diff

67
tests/test_api.py Normal file
View File

@ -0,0 +1,67 @@
from falcon import testing
import json
import pytest
from dspace_statistics_api.app import api
@pytest.fixture
def client():
return testing.TestClient(api)
def test_get_docs(client):
'''Test requesting the documentation at the root.'''
response = client.simulate_get('/')
assert isinstance(response.content, bytes)
assert response.status_code == 200
def test_get_item(client):
'''Test requesting a single item.'''
response = client.simulate_get('/item/17')
response_doc = json.loads(response.text)
assert isinstance(response_doc['downloads'], int)
assert isinstance(response_doc['id'], int)
assert isinstance(response_doc['views'], int)
assert response.status_code == 200
def test_get_missing_item(client):
'''Test requesting a single non-existing item.'''
response = client.simulate_get('/item/1')
assert response.status_code == 404
def test_get_items(client):
'''Test requesting 100 items.'''
response = client.simulate_get('/items', query_string='limit=100')
response_doc = json.loads(response.text)
assert isinstance(response_doc['currentPage'], int)
assert isinstance(response_doc['totalPages'], int)
assert isinstance(response_doc['statistics'], list)
assert response.status_code == 200
def test_get_items_invalid_limit(client):
'''Test requesting 100 items with an invalid limit parameter.'''
response = client.simulate_get('/items', query_string='limit=101')
assert response.status_code == 400
def test_get_items_invalid_page(client):
'''Test requesting 100 items with an invalid page parameter.'''
response = client.simulate_get('/items', query_string='page=-1')
assert response.status_code == 400