diff --git a/Makefile b/Makefile index 659af782..fe5a560e 100644 --- a/Makefile +++ b/Makefile @@ -54,6 +54,7 @@ integration-tests: DATA_API_ROOT_URL=https://data.localhost.ai \ TEST_API_KEY='' \ TEST_BEARER_TOKEN='' \ + TEST_COMPANY_ID='-1' \ coverage run -m pytest --vcr-record=none $(test_path) ## coverage: Display code coverage in the console. diff --git a/setup.cfg b/setup.cfg index addc8de2..bfe79078 100644 --- a/setup.cfg +++ b/setup.cfg @@ -15,7 +15,7 @@ parallel = True [coverage:report] precision = 2 -fail_under = 98.45 +fail_under = 98.47 skip_covered = True show_missing = True exclude_lines = diff --git a/src/corva/api_adapter.py b/src/corva/api_adapter.py index 3a79aca5..fcefc3be 100644 --- a/src/corva/api_adapter.py +++ b/src/corva/api_adapter.py @@ -158,6 +158,31 @@ def insert( return InsertResult.parse_obj(response.json()) + def replace(self, provider: str, dataset: str, id_: str, *, document: dict) -> dict: + """Replaces all document data. + + Replace all document data using the endpoint PUT + 'data/{provider}/{dataset}/{id}/'. + + Args: + provider: company name owning the dataset. + dataset: dataset name. + id_: document id to replace. + document: new document data. + + Raises: + httpx.HTTPStatusError: if request was unsuccessful. + + Returns: + Updated document. + """ + + response = self.http.put(url=f"data/{provider}/{dataset}/{id_}/", json=document) + + response.raise_for_status() + + return response.json() + class PlatformApiV1Sdk: def __init__(self, client: httpx.Client): diff --git a/src/corva/configuration.py b/src/corva/configuration.py index 42552747..128180cc 100644 --- a/src/corva/configuration.py +++ b/src/corva/configuration.py @@ -44,3 +44,9 @@ def get_test_dataset() -> str: """Dataset for testing""" return os.environ['TEST_DATASET'] + + +def get_test_company_id() -> int: + """Company id for testing""" + + return int(os.environ['TEST_COMPANY_ID']) diff --git a/tests/integration/cassettes/TestUserApiSdk.test_replace.yaml b/tests/integration/cassettes/TestUserApiSdk.test_replace.yaml new file mode 100644 index 00000000..149b542d --- /dev/null +++ b/tests/integration/cassettes/TestUserApiSdk.test_replace.yaml @@ -0,0 +1,320 @@ +interactions: +- request: + body: '{"well": {"name": "deleteme-python-sdk-autotest-d4a1214c"}}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '59' + content-type: + - application/json + user-agent: + - python-httpx/0.22.0 + method: POST + uri: null + response: + content: '{"data":{"id":"328568","type":"well","attributes":{"name":"deleteme-python-sdk-autotest-d4a1214c","status":"unknown","state":"planned"},"relationships":{}}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Mon, 23 May 2022 11:41:15 GMT + ETag: + - W/"11b6d1d9eb9720313d1fd72196df9595" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Rack-CORS: + - miss; no-origin + X-Request-Id: + - ce585d4e-d4dc-47bb-9f2a-f109e6a3a59b + X-Runtime: + - '0.101889' + X-XSS-Protection: + - 1; mode=block + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + user-agent: + - python-httpx/0.22.0 + method: GET + uri: null + response: + content: '{"data":{"id":"328568","type":"well","attributes":{"asset_id":30719716},"relationships":{}}}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Mon, 23 May 2022 11:41:15 GMT + ETag: + - W/"b161ea51687ef8fd4b47fd4dc1ca754d" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Rack-CORS: + - miss; no-origin + X-Request-Id: + - 05e9b6f6-ab30-4478-8f0d-dfe7e7679ac7 + X-Runtime: + - '0.044065' + X-XSS-Protection: + - 1; mode=block + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + user-agent: + - python-httpx/0.22.0 + method: DELETE + uri: null + response: + content: '{"deleted_count":0}' + headers: + Connection: + - keep-alive + Content-Length: + - '19' + Content-Type: + - application/json + Date: + - Mon, 23 May 2022 11:41:16 GMT + server: + - uvicorn + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '[{"asset_id": 30719716, "version": 1, "data": {"k1": "v1"}, "timestamp": + 10}]' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '77' + content-type: + - application/json + user-agent: + - python-httpx/0.22.0 + method: POST + uri: null + response: + content: '{"inserted_ids":["628b72dcd6aff355ccea8b78"],"failed_count":0,"messages":[]}' + headers: + Connection: + - keep-alive + Content-Length: + - '76' + Content-Type: + - application/json + Date: + - Mon, 23 May 2022 11:41:16 GMT + server: + - uvicorn + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '{"data": {"k2": "v2"}, "version": 2, "company_id": 80, "timestamp": 11, + "asset_id": 30719716}' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '93' + content-type: + - application/json + user-agent: + - python-httpx/0.22.0 + x-corva-app: + - python-sdk-autotest-2022-05-23 11:41:14+00:00 + method: PUT + uri: null + response: + content: '{"_id":"628b72dcd6aff355ccea8b78","company_id":80,"asset_id":30719716,"version":2,"provider":"big-data-energy","collection":"python-sdk-autotests","data":{"k2":"v2"},"timestamp":11,"app_key":"big-data-energy.python_sdk_app_for_autotests"}' + headers: + Connection: + - keep-alive + Content-Length: + - '238' + Content-Type: + - application/json + Date: + - Mon, 23 May 2022 11:41:17 GMT + server: + - uvicorn + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + user-agent: + - python-httpx/0.22.0 + method: GET + uri: null + response: + content: '{"_id":"628b72dcd6aff355ccea8b78","company_id":80,"asset_id":30719716,"version":2,"provider":"big-data-energy","collection":"python-sdk-autotests","data":{"k2":"v2"},"timestamp":11,"app_key":"big-data-energy.python_sdk_app_for_autotests"}' + headers: + Connection: + - keep-alive + Content-Length: + - '238' + Content-Type: + - application/json + Date: + - Mon, 23 May 2022 11:41:17 GMT + server: + - uvicorn + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + user-agent: + - python-httpx/0.22.0 + method: DELETE + uri: null + response: + content: '{"status":"deleted"}' + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Connection: + - keep-alive + Content-Encoding: + - gzip + Content-Type: + - application/json; charset=utf-8 + Date: + - Mon, 23 May 2022 11:41:18 GMT + ETag: + - W/"4df20f95e824b2af44a61642d88daaf0" + Referrer-Policy: + - strict-origin-when-cross-origin + Server: + - nginx/1.18.0 + Transfer-Encoding: + - chunked + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Download-Options: + - noopen + X-Frame-Options: + - SAMEORIGIN + X-Permitted-Cross-Domain-Policies: + - none + X-Rack-CORS: + - miss; no-origin + X-Request-Id: + - db6d7a71-cce6-4279-8050-eb7c52cecd26 + X-Runtime: + - '0.073215' + X-XSS-Protection: + - 1; mode=block + http_version: HTTP/1.1 + status_code: 200 +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + user-agent: + - python-httpx/0.22.0 + method: DELETE + uri: null + response: + content: '{"deleted_count":1}' + headers: + Connection: + - keep-alive + Content-Length: + - '19' + Content-Type: + - application/json + Date: + - Mon, 23 May 2022 11:41:18 GMT + server: + - uvicorn + http_version: HTTP/1.1 + status_code: 200 +version: 1 diff --git a/tests/integration/test_api.py b/tests/integration/test_api.py index 411b0421..73fcd46f 100644 --- a/tests/integration/test_api.py +++ b/tests/integration/test_api.py @@ -153,6 +153,11 @@ def dataset() -> str: return corva.configuration.get_test_dataset() +@pytest.fixture(scope='module') +def company_id() -> int: + return corva.configuration.get_test_company_id() + + @pytest.fixture(scope='module') def app_key() -> str: now = datetime.datetime.now(tz=datetime.timezone.utc).replace(microsecond=0) @@ -326,3 +331,52 @@ def test_insert( collection = response.json() assert len(collection) == 2 + + @pytest.mark.vcr + def test_replace( + self, + setup_: Iterable[int], + sdk: corva.api_adapter.UserApiSdk, + dataset: str, + provider: str, + company_id: int, + data: httpx.Client, + ): + with setup_ as asset_id: + response = data.post( + f'data/{provider}/{dataset}/', + json=[ + { + "asset_id": asset_id, + "version": 1, + "data": {"k1": "v1"}, + "timestamp": 10, + }, + ], + ) + response.raise_for_status() + id_ = response.json()['inserted_ids'][0] + + with sdk as s: + s.data.v1.replace( + provider=provider, + dataset=dataset, + id_=id_, + document={ + 'data': {'k2': 'v2'}, + 'version': 2, + 'company_id': company_id, + 'timestamp': 11, + 'asset_id': asset_id, + }, + ) + + response = data.get( + url=f"data/{provider}/{dataset}/{id_}/", + ) + response.raise_for_status() + document_after_replace = response.json() + + assert document_after_replace['data'] == {'k2': 'v2'} + assert document_after_replace['version'] == 2 + assert document_after_replace['timestamp'] == 11