diff --git a/tests/test_fixity.py b/tests/test_fixity.py new file mode 100644 index 0000000..3a5e355 --- /dev/null +++ b/tests/test_fixity.py @@ -0,0 +1,472 @@ +import calendar +import json +import uuid +from datetime import datetime +from unittest import mock + +import pytest +import requests + +from fixity import fixity +from fixity import reporting +from fixity.models import Report +from fixity.models import Session + +SESSION = Session() +STORAGE_SERVICE_URL = "http://localhost:8000/" +STORAGE_SERVICE_USER = "test" +STORAGE_SERVICE_KEY = "dfe83300db5f05f63157f772820bb028bd4d0e27" + + +@mock.patch( + "requests.get", + side_effect=[ + mock.Mock( + **{ + "status_code": 200, + "json.return_value": {}, + }, + spec=requests.Response, + ), + mock.Mock( + **{ + "status_code": 200, + "json.return_value": { + "success": True, + "message": "", + "failures": { + "files": {"missing": [], "changed": [], "untracked": []} + }, + "timestamp": None, + }, + }, + spec=requests.Response, + ), + ], +) +def test_scan(_get): + aip_id = uuid.uuid4() + + response = fixity.scan( + aip=str(aip_id), + ss_url=STORAGE_SERVICE_URL, + ss_user=STORAGE_SERVICE_USER, + ss_key=STORAGE_SERVICE_KEY, + session=SESSION, + ) + + assert response is True + + assert _get.mock_calls == [ + mock.call( + f"{STORAGE_SERVICE_URL}api/v2/file/{aip_id}/", + params={"username": STORAGE_SERVICE_USER, "api_key": STORAGE_SERVICE_KEY}, + ), + mock.call( + f"{STORAGE_SERVICE_URL}api/v2/file/{aip_id}/check_fixity/", + params={"username": STORAGE_SERVICE_USER, "api_key": STORAGE_SERVICE_KEY}, + ), + ] + + +@mock.patch( + "requests.get", + side_effect=[ + mock.Mock( + **{ + "status_code": 200, + "json.return_value": {}, + }, + spec=requests.Response, + ), + mock.Mock( + **{ + "status_code": 200, + "json.return_value": { + "success": True, + "message": "", + "failures": { + "files": {"missing": [], "changed": [], "untracked": []} + }, + "timestamp": None, + }, + }, + spec=requests.Response, + ), + ], +) +@mock.patch( + "requests.post", + side_effect=[ + mock.Mock(status_code=201, spec=requests.Response), + mock.Mock(status_code=201, spec=requests.Response), + ], +) +def test_scan_if_report_url_exist(_post: mock.Mock, _get: mock.Mock): + aip_id = uuid.uuid4() + + response = fixity.scan( + aip=str(aip_id), + ss_url="", + ss_user=STORAGE_SERVICE_USER, + ss_key=STORAGE_SERVICE_KEY, + session=SESSION, + report_url=STORAGE_SERVICE_URL, + ) + + assert response is True + + start_time = int(calendar.timegm(datetime.utcnow().utctimetuple())) + assert _get.mock_calls == [ + mock.call( + f"api/v2/file/{aip_id}/", + params={"username": STORAGE_SERVICE_USER, "api_key": STORAGE_SERVICE_KEY}, + ), + mock.call( + f"api/v2/file/{aip_id}/check_fixity/", + params={"username": STORAGE_SERVICE_USER, "api_key": STORAGE_SERVICE_KEY}, + ), + ] + assert _post.call_count == 2 + assert _post.mock_calls == [ + mock.call( + f"{STORAGE_SERVICE_URL}api/fixity/{aip_id}", + data=json.dumps({"started": start_time}), + headers={"Content-Type": "application/json"}, + ), + mock.call( + f"{STORAGE_SERVICE_URL}api/fixity/{aip_id}", + data=json.dumps( + { + "success": True, + "message": "", + "failures": { + "files": {"missing": [], "changed": [], "untracked": []} + }, + "timestamp": None, + "started": start_time, + "finished": start_time, + } + ), + headers={"Content-Type": "application/json"}, + ), + ] + + +@mock.patch( + "requests.get", + side_effect=[ + mock.Mock( + **{ + "status_code": 200, + "json.return_value": {}, + }, + spec=requests.Response, + ), + mock.Mock( + **{ + "status_code": 200, + "json.return_value": { + "success": True, + "message": "", + "failures": { + "files": {"missing": [], "changed": [], "untracked": []} + }, + "timestamp": None, + }, + }, + spec=requests.Response, + ), + ], +) +@mock.patch( + "requests.post", + side_effect=[ + mock.Mock( + status_code=404, + spec=requests.Response, + side_effect=reporting.ReportServiceException, + ), + mock.Mock( + status_code=500, + spec=requests.Response, + side_effect=reporting.ReportServiceException, + ), + ], +) +def test_scan_handle_exceptions_if_report_url_exist( + _post: mock.Mock, _get: mock.Mock, capsys +): + aip_id = uuid.uuid4() + + response = fixity.scan( + aip=str(aip_id), + ss_url="", + ss_user=STORAGE_SERVICE_USER, + ss_key=STORAGE_SERVICE_KEY, + session=SESSION, + report_url=STORAGE_SERVICE_URL, + ) + + assert response is True + + captured = capsys.readouterr() + assert f"Unable to POST pre-scan report to {STORAGE_SERVICE_URL}" in captured.err + assert f"Unable to POST report for AIP {aip_id} to remote service" in captured.err + + +@mock.patch( + "requests.get", + side_effect=[ + mock.Mock( + **{ + "status_code": 200, + "json.return_value": {}, + }, + spec=requests.Response, + ), + mock.Mock( + **{ + "status_code": 500, + "json.return_value": {}, + }, + spec=requests.Response, + ), + ], +) +def test_scan_handle_exceptions(_get, capsys): + aip_id = uuid.uuid4() + + response = fixity.scan( + aip=str(aip_id), + ss_url=STORAGE_SERVICE_URL, + ss_user=STORAGE_SERVICE_USER, + ss_key=STORAGE_SERVICE_KEY, + session=SESSION, + ) + + assert response is None + + captured = capsys.readouterr() + assert ( + f'Storage service at "{STORAGE_SERVICE_URL}" encountered an internal error while scanning AIP {aip_id}\n' + == captured.err + ) + + +@mock.patch( + "requests.get", + side_effect=[ + mock.Mock( + **{ + "status_code": 200, + "json.return_value": {}, + }, + spec=requests.Response, + ), + mock.Mock( + **{ + "json.return_value": {}, + }, + spec=requests.Response, + side_effect=ConnectionError, + ), + ], +) +def test_scan_handle_exceptions_if_no_scan_attempted(_get): + aip_id = uuid.uuid4() + + response = fixity.scan( + aip=str(aip_id), + ss_url=STORAGE_SERVICE_URL, + ss_user=STORAGE_SERVICE_USER, + ss_key=STORAGE_SERVICE_KEY, + session=SESSION, + ) + + assert response is None + assert Report(aip_id=aip_id).success is None + + +@pytest.mark.parametrize( + "status, error_message", + [ + (True, "succeeded"), + (False, "failed"), + (None, "didn't run"), + ], + ids=["Success", "Fail", "Didnot run"], +) +def test_scan_message(status, error_message): + aip_id = uuid.uuid4() + + response = fixity.scan_message( + aip_uuid=aip_id, status=status, message=error_message + ) + + assert ( + response == f"Fixity scan {error_message} for AIP: {aip_id} ({error_message})" + ) + + +@mock.patch( + "requests.get", + side_effect=[ + mock.Mock( + **{ + "status_code": 200, + "json.return_value": { + "meta": {"next": None}, + "objects": [ + { + "package_type": "AIP", + "status": "UPLOADED", + "uuid": str(uuid.uuid4()), + }, + { + "package_type": "AIP", + "status": "UPLOADED", + "uuid": str(uuid.uuid4()), + }, + ], + }, + }, + spec=requests.Response, + ), + mock.Mock( + **{ + "status_code": 200, + "json.return_value": {}, + }, + spec=requests.Response, + ), + mock.Mock( + **{ + "status_code": 200, + "json.return_value": { + "success": True, + "message": "", + "failures": { + "files": {"missing": [], "changed": [], "untracked": []} + }, + "timestamp": None, + }, + }, + spec=requests.Response, + ), + mock.Mock( + **{ + "status_code": 200, + "json.return_value": {}, + }, + spec=requests.Response, + ), + mock.Mock( + **{ + "status_code": 200, + "json.return_value": { + "success": True, + "message": "", + "failures": { + "files": {"missing": [], "changed": [], "untracked": []} + }, + "timestamp": None, + }, + }, + spec=requests.Response, + ), + ], +) +def test_scanall(_get, capsys): + response = fixity.scanall( + ss_url=STORAGE_SERVICE_URL, + ss_user=STORAGE_SERVICE_USER, + ss_key=STORAGE_SERVICE_KEY, + session=SESSION, + ) + + assert response is True + + captured = capsys.readouterr() + assert "Successfully scanned 2 AIPs" in captured.err + + +@mock.patch( + "requests.get", + side_effect=[ + mock.Mock( + **{ + "status_code": 200, + "json.return_value": { + "meta": {"next": None}, + "objects": [ + { + "package_type": "AIP", + "status": "UPLOADED", + "uuid": str(uuid.uuid4()), + }, + { + "package_type": "AIP", + "status": "UPLOADED", + "uuid": str(uuid.uuid4()), + }, + ], + }, + }, + spec=requests.Response, + ), + mock.Mock( + **{ + "status_code": 401, + "json.return_value": {}, + }, + spec=requests.Response, + side_effect=Exception, + ), + mock.Mock( + **{ + "status_code": 200, + "json.return_value": { + "success": True, + "message": "", + "failures": { + "files": {"missing": [], "changed": [], "untracked": []} + }, + "timestamp": None, + }, + }, + spec=requests.Response, + ), + mock.Mock( + **{ + "status_code": 401, + "json.return_value": {}, + }, + spec=requests.Response, + side_effect=Exception, + ), + mock.Mock( + **{ + "status_code": 200, + "json.return_value": { + "success": True, + "message": "", + "failures": { + "files": {"missing": [], "changed": [], "untracked": []} + }, + "timestamp": None, + }, + }, + spec=requests.Response, + ), + ], +) +def test_scanall_handle_exceptions(_get): + response = fixity.scanall( + ss_url=STORAGE_SERVICE_URL, + ss_user=STORAGE_SERVICE_USER, + ss_key=STORAGE_SERVICE_KEY, + session=SESSION, + ) + + assert response is False