diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 1ed6cc55..a9889071 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -35,11 +35,8 @@ jobs: black --check src docs oas-up-to-date: - name: Check for unexepected OAS changes + name: Check for unexpected OAS changes runs-on: ubuntu-latest - strategy: - matrix: - version: ['v1', 'v2'] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -60,18 +57,18 @@ jobs: run: pip install -r requirements/ci.txt - name: Generate OAS files - run: ./bin/generate_schema.sh ${{ matrix.version }} openapi-${{ matrix.version }}.yaml + run: ./bin/generate_schema.sh openapi.yaml env: DJANGO_SETTINGS_MODULE: objects.conf.ci - name: Check for OAS changes run: | - diff openapi-${{ matrix.version }}.yaml src/objects/api/${{ matrix.version }}/openapi.yaml + diff openapi.yaml src/objects/api/v2/openapi.yaml - name: Write failure markdown if: ${{ failure() }} run: | echo 'Run the following command locally and commit the changes' >> $GITHUB_STEP_SUMMARY echo '' >> $GITHUB_STEP_SUMMARY echo '```bash' >> $GITHUB_STEP_SUMMARY - echo './bin/generate_schema.sh ${{ matrix.version }}' >> $GITHUB_STEP_SUMMARY + echo './bin/generate_schema.sh' >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/generate-postman-collection.yml b/.github/workflows/generate-postman-collection.yml index 6d4e844f..b0bca4ac 100644 --- a/.github/workflows/generate-postman-collection.yml +++ b/.github/workflows/generate-postman-collection.yml @@ -12,11 +12,7 @@ on: jobs: run: runs-on: ubuntu-latest - strategy: - matrix: - version: ['v1', 'v2'] - - name: Run with version ${{ matrix.version }} + name: Generate Postman collection steps: - uses: actions/checkout@v4 @@ -29,4 +25,4 @@ jobs: - name: Create tests folder run: mkdir -p ./tests/postman - name: Generate Postman collection - run: openapi2postmanv2 -s ./src/objects/api/${{ matrix.version }}/openapi.yaml -o ./tests/postman/collection.json --pretty + run: openapi2postmanv2 -s ./src/objects/api/v2/openapi.yaml -o ./tests/postman/collection.json --pretty diff --git a/.github/workflows/generate-sdks.yml b/.github/workflows/generate-sdks.yml index 2fcd7988..959a9c25 100644 --- a/.github/workflows/generate-sdks.yml +++ b/.github/workflows/generate-sdks.yml @@ -12,11 +12,7 @@ on: jobs: run: runs-on: ubuntu-latest - strategy: - matrix: - version: [ 'v1', 'v2' ] - - name: Run with version ${{ matrix.version }} + name: Generate SDKs steps: - uses: actions/checkout@v4 @@ -28,7 +24,7 @@ jobs: run: npm install -g @openapitools/openapi-generator-cli - name: Determing oas path id: vars - run: echo ::set-output name=oas::./src/objects/api/${{ matrix.version }}/openapi.yaml + run: echo ::set-output name=oas::./src/objects/api/v2/openapi.yaml - name: Validate schema run: openapi-generator-cli validate -i ${{ steps.vars.outputs.oas }} - name: Generate Java client diff --git a/.github/workflows/lint-oas.yml b/.github/workflows/lint-oas.yml index 8767511b..850478e3 100644 --- a/.github/workflows/lint-oas.yml +++ b/.github/workflows/lint-oas.yml @@ -12,11 +12,7 @@ on: jobs: run: runs-on: ubuntu-latest - strategy: - matrix: - version: [ 'v1', 'v2' ] - - name: Run with version ${{ matrix.version }} + name: Lint OAS steps: - uses: actions/checkout@v4 @@ -27,4 +23,4 @@ jobs: - name: Install spectral run: npm install -g @stoplight/spectral@5 - name: Run OAS linter - run: spectral lint ./src/objects/api/${{ matrix.version }}/openapi.yaml + run: spectral lint ./src/objects/api/v2/openapi.yaml diff --git a/README.NL.rst b/README.NL.rst index bfb53159..ffffeb58 100644 --- a/README.NL.rst +++ b/README.NL.rst @@ -57,20 +57,6 @@ latest n/a `ReDoc `_, `Swagger `_ (`verschillen `_) -1.3.0 2021-01-12 `ReDoc `_, - `Swagger `_ - (`verschillen `_) -1.2.0 2021-09-22 `ReDoc `_, - `Swagger `_ - (`verschillen `_) -1.1.1 2021-06-22 `ReDoc `_, - `Swagger `_ - (`verschillen `_) -1.1.0 2021-04-21 `ReDoc `_, - `Swagger `_ - (`verschillen `_) -1.0.0 2021-01-13 `ReDoc `_, - `Swagger `_ ============== ============== ============================= Vorige versies worden nog 6 maanden ondersteund nadat de volgende versie is uitgebracht. diff --git a/README.rst b/README.rst index bb3f6544..85b2e75f 100644 --- a/README.rst +++ b/README.rst @@ -55,20 +55,6 @@ latest n/a `ReDoc `_, `Swagger `_ (`diff `_) -1.3.0 2021-01-12 `ReDoc `_, - `Swagger `_ - (`diff `_) -1.2.0 2021-09-22 `ReDoc `_, - `Swagger `_ - (`diff `_) -1.1.1 2021-06-22 `ReDoc `_, - `Swagger `_ - (`diff `_) -1.1.0 2021-04-21 `ReDoc `_, - `Swagger `_ - (`diff `_) -1.0.0 2021-01-13 `ReDoc `_, - `Swagger `_ ============== ============== ============================= Previous versions are supported for 6 month after the next version is released. diff --git a/bin/generate_schema.sh b/bin/generate_schema.sh index d24ece5b..61907011 100755 --- a/bin/generate_schema.sh +++ b/bin/generate_schema.sh @@ -4,18 +4,9 @@ # # Run this script from the root of the repository -if [ "$1" = "" ]; then - echo "You need to pass a version in the first argument" - exit 1 -fi -if [ "$1" != "v1" ] && [ "$1" != "v2" ]; then - echo "You need to pass a correct version in the first argument. Available values: v1, v2" - exit 1 -fi +export SCHEMA_PATH=src/objects/api/v2/openapi.yaml -export SCHEMA_PATH=src/objects/api/$1/openapi.yaml +OUTPUT_FILE=$1 -OUTPUT_FILE=$2 - -src/manage.py spectacular --file ${OUTPUT_FILE:-$SCHEMA_PATH} --validate --api-version $1 +src/manage.py spectacular --file ${OUTPUT_FILE:-$SCHEMA_PATH} --validate diff --git a/src/objects/api/urls.py b/src/objects/api/urls.py index 1c5ba014..6444483e 100644 --- a/src/objects/api/urls.py +++ b/src/objects/api/urls.py @@ -1,6 +1,5 @@ from django.urls import include, path urlpatterns = [ - path("v1", include("objects.api.v1.urls", namespace="v1")), path("v2", include("objects.api.v2.urls", namespace="v2")), ] diff --git a/src/objects/api/v1/__init__.py b/src/objects/api/v1/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/objects/api/v1/filters.py b/src/objects/api/v1/filters.py deleted file mode 100644 index 4bdb24ac..00000000 --- a/src/objects/api/v1/filters.py +++ /dev/null @@ -1,121 +0,0 @@ -from datetime import date as date_ - -from django import forms -from django.utils.translation import gettext_lazy as _ - -from django_filters import filters -from rest_framework import serializers -from vng_api_common.filtersets import FilterSet - -from objects.core.models import ObjectRecord, ObjectType -from objects.utils.filters import ObjectTypeFilter - -from ..constants import Operators -from ..utils import display_choice_values_for_help_text, string_to_value -from ..validators import validate_data_attrs - - -class ObjectRecordFilterForm(forms.Form): - def clean(self): - cleaned_data = super().clean() - date = cleaned_data.get("date") - registration_date = cleaned_data.get("registrationDate") - - if date and registration_date: - raise serializers.ValidationError( - _( - "'date' and 'registrationDate' parameters can't be used in the same request" - ), - code="invalid-date-query-params", - ) - - return cleaned_data - - -class ObjectRecordFilterSet(FilterSet): - type = ObjectTypeFilter( - field_name="object__object_type", - help_text=_("Url reference to OBJECTTYPE in Objecttypes API"), - queryset=ObjectType.objects.all(), - min_length=1, - max_length=1000, - ) - date = filters.DateFilter( - method="filter_date", - help_text=_( - "Display record data for the specified material date, i.e. the specified " - "date would be between `startAt` and `endAt` attributes. The default value is today" - ), - ) - registrationDate = filters.DateFilter( - method="filter_registration_date", - help_text=_( - "Display record data for the specified registration date, i.e. the specified " - "date would be between `registrationAt` attributes of different records" - ), - ) - data_attrs = filters.CharFilter( - method="filter_data_attrs", - validators=[validate_data_attrs], - help_text=_( - """Only include objects that have attributes with certain values. -Data filtering expressions are comma-separated and are structured as follows: -A valid parameter value has the form `key__operator__value`. -`key` is the attribute name, `operator` is the comparison operator to be used and `value` is the attribute value. -Note: Values can be string, numeric, or dates (ISO format; YYYY-MM-DD). - -Valid operator values are: -%(operator_choices)s - -`value` may not contain double underscore or comma characters. -`key` may not contain comma characters and includes double underscore only if it indicates nested attributes. - -Example: in order to display only objects with `height` equal to 100, query `data_attrs=height__exact__100` -should be used. If `height` is nested inside `dimensions` attribute, query should look like -`data_attrs=dimensions__height__exact__100` -""" - ) - % {"operator_choices": display_choice_values_for_help_text(Operators)}, - ) - - class Meta: - model = ObjectRecord - fields = ("type", "data_attrs", "date", "registrationDate") - form = ObjectRecordFilterForm - - def filter_data_attrs(self, queryset, name, value: str): - parts = value.split(",") - - for value_part in parts: - variable, operator, str_value = value_part.rsplit("__", 2) - real_value = string_to_value(str_value) - - if operator == "exact": - # for exact operator try to filter on string and numeric values - in_vals = [str_value] - if real_value != value: - in_vals.append(real_value) - queryset = queryset.filter(**{f"data__{variable}__in": in_vals}) - elif operator == "icontains": - # icontains treats everything like strings - queryset = queryset.filter( - **{f"data__{variable}__icontains": str_value} - ) - elif operator == "in": - # in must be a list - values = str_value.split("|") - queryset = queryset.filter(**{f"data__{variable}__in": values}) - - else: - # gt, gte, lt, lte operators - queryset = queryset.filter( - **{f"data__{variable}__{operator}": real_value} - ) - - return queryset - - def filter_date(self, queryset, name, value: date_): - return queryset.filter_for_date(value) - - def filter_registration_date(self, queryset, name, value: date_): - return queryset.filter_for_registration_date(value) diff --git a/src/objects/api/v1/openapi.yaml b/src/objects/api/v1/openapi.yaml deleted file mode 100644 index 22c70e7f..00000000 --- a/src/objects/api/v1/openapi.yaml +++ /dev/null @@ -1,919 +0,0 @@ -openapi: 3.0.3 -info: - title: Objects API - version: 1.3.0 (v1) - description: | - An API to manage Objects. - - # Introduction - - An OBJECT is of a certain OBJECTTYPE (defined in the Objecttypes API). An - OBJECT has a few core attributes that every OBJECT (technically a RECORD, - see below) has, although these attributes can sometimes be empty. They are - attributes like `geometry` and some administrative attributes. The data that - describes the actual object is stored in the `data` attribute and follows - the JSON schema as given by the OBJECTTYPE. - - ## Validation - - When an OBJECT is created or changed the `OBJECT.type` attribute refers to the - matching OBJECTTYPE in the Objecttypes API. The RECORD always indicates which - OBJECTTYPE-VERSION is used, shown in the `RECORD.typeVersion` attribute. - - Using these 2 attributes, the appropriate JSON schema is retrieved from the - Objecttypes API and the OBJECT data is validated against this JSON schema. - - ## History - - Each OBJECT has 1 or more RECORDs. A RECORD contains the data of an OBJECT - at a certain time. An OBJECT can have multiple RECORDS that describe the - history of that OBJECT. Changes to an OBJECT actually create a new RECORD - under the OBJECT and leaves the old RECORD as is. - - ### Material and formal history - - History can be seen from 2 perspectives: formal and material history. The - formal history describes the history as it should be (stored in the - `startAt` and `endAt` attributes). The material history describes the - history as it was administratively processed (stored in the `registeredAt` - attribute). - - The difference is that an object could be created or updated in the real - world at a certain point in time but the administrative change (ie. save or - update the object in the Objects API) can be done at a later time. The - query parameters `?date=2021-01-01` (formal history) and - `?registrationDate=2021-01-01` (material history) allow for querying the - RECORDS as seen from both perspectives, and can yield different results. - - ### Corrections - - RECORDs cannot be deleted or changed once saved. If an error was made to - a RECORD, the RECORD can be "corrected" by saving a new RECORD and indicate - that it corrects a previous RECORD. This is done via the attribute - `correctionFor`. - - ### Deletion - - Although OBJECTs can be deleted, it is sometimes better to set the - `endDate` of an OBJECT. Deleting an OBJECT also deletes all RECORDs in - accordance with privacy laws. - - # Authorizations - - The API uses API-tokens that grant certain permissions. The API-token is - passed via a header, like this: `Authorization: Token ` - - # Notifications - - When OBJECTs are created, updated or deleted via the API, notifications of - these operations are published to the configured Notifications API in the - `objecten` channel. - contact: - url: https://github.com/maykinmedia/objects-api - license: - name: EUPL-1.2 -paths: - /objects: - get: - operationId: object_list - description: Retrieve a list of OBJECTs and their actual RECORD. The actual - record is defined as if the query parameter `date=` was given. - parameters: - - in: header - name: Accept-Crs - schema: - type: string - enum: - - EPSG:4326 - description: 'The desired ''Coordinate Reference System'' (CRS) of the response - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 is - the same as WGS84).' - - in: query - name: data_attrs - schema: - type: string - description: | - Only include objects that have attributes with certain values. - Data filtering expressions are comma-separated and are structured as follows: - A valid parameter value has the form `key__operator__value`. - `key` is the attribute name, `operator` is the comparison operator to be used and `value` is the attribute value. - Note: Values can be string, numeric, or dates (ISO format; YYYY-MM-DD). - - Valid operator values are: - * `exact` - equal to - * `gt` - greater than - * `gte` - greater than or equal to - * `lt` - lower than - * `lte` - lower than or equal to - * `icontains` - case-insensitive partial match - * `in` - in a list of values separated by `|` - - `value` may not contain double underscore or comma characters. - `key` may not contain comma characters and includes double underscore only if it indicates nested attributes. - - Example: in order to display only objects with `height` equal to 100, query `data_attrs=height__exact__100` - should be used. If `height` is nested inside `dimensions` attribute, query should look like - `data_attrs=dimensions__height__exact__100` - - in: query - name: date - schema: - type: string - format: date - description: Display record data for the specified material date, i.e. the - specified date would be between `startAt` and `endAt` attributes. The default - value is today - - in: query - name: fields - schema: - type: string - description: 'Comma-separated fields, which should be displayed in the response. - For example: ''url, uuid, record__geometry''.' - - in: query - name: registrationDate - schema: - type: string - format: date - description: Display record data for the specified registration date, i.e. - the specified date would be between `registrationAt` attributes of different - records - - in: query - name: type - schema: - type: string - format: uri - maxLength: 1000 - minLength: 1 - description: Url reference to OBJECTTYPE in Objecttypes API - tags: - - objects - security: - - tokenAuth: [] - responses: - '200': - headers: - Content-Crs: - schema: - type: string - enum: - - EPSG:4326 - description: 'The ''Coordinate Reference System'' (CRS) of the request - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 - is the same as WGS84).' - X-Unauthorized-Fields: - schema: - type: string - description: 'List of fields that are not allowed to display if the - field-based authorization is turned on. The value has the following - format: `objectType1:fieldA,fieldB; objectType2:fieldC,fieldD`' - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Object' - description: OK - post: - operationId: object_create - description: Create an OBJECT and its initial RECORD. - parameters: - - in: header - name: Accept-Crs - schema: - type: string - enum: - - EPSG:4326 - description: 'The desired ''Coordinate Reference System'' (CRS) of the response - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 is - the same as WGS84).' - - in: header - name: Content-Crs - schema: - type: string - enum: - - EPSG:4326 - description: 'The ''Coordinate Reference System'' (CRS) of the request data. - According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 is the same - as WGS84).' - required: true - - in: header - name: Content-Type - schema: - type: string - enum: - - application/json - description: Content type of the request body. - required: true - tags: - - objects - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Object' - required: true - security: - - tokenAuth: [] - responses: - '201': - headers: - Content-Crs: - schema: - type: string - enum: - - EPSG:4326 - description: 'The ''Coordinate Reference System'' (CRS) of the request - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 - is the same as WGS84).' - content: - application/json: - schema: - $ref: '#/components/schemas/Object' - description: Created - /objects/{uuid}: - get: - operationId: object_read - description: Retrieve a single OBJECT and its actual RECORD. The actual record - is defined as if the query parameter `date=` was given. - parameters: - - in: header - name: Accept-Crs - schema: - type: string - enum: - - EPSG:4326 - description: 'The desired ''Coordinate Reference System'' (CRS) of the response - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 is - the same as WGS84).' - - in: query - name: fields - schema: - type: string - description: 'Comma-separated fields, which should be displayed in the response. - For example: ''url, uuid, record__geometry''.' - - in: path - name: uuid - schema: - type: string - format: uuid - description: Unique identifier (UUID4) - required: true - tags: - - objects - security: - - tokenAuth: [] - responses: - '200': - headers: - Content-Crs: - schema: - type: string - enum: - - EPSG:4326 - description: 'The ''Coordinate Reference System'' (CRS) of the request - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 - is the same as WGS84).' - X-Unauthorized-Fields: - schema: - type: string - description: 'List of fields that are not allowed to display if the - field-based authorization is turned on. The value has the following - format: `objectType1:fieldA,fieldB; objectType2:fieldC,fieldD`' - content: - application/json: - schema: - $ref: '#/components/schemas/Object' - description: OK - put: - operationId: object_update - description: Update the OBJECT by creating a new RECORD with the updates values. - parameters: - - in: header - name: Accept-Crs - schema: - type: string - enum: - - EPSG:4326 - description: 'The desired ''Coordinate Reference System'' (CRS) of the response - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 is - the same as WGS84).' - - in: header - name: Content-Crs - schema: - type: string - enum: - - EPSG:4326 - description: 'The ''Coordinate Reference System'' (CRS) of the request data. - According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 is the same - as WGS84).' - required: true - - in: header - name: Content-Type - schema: - type: string - enum: - - application/json - description: Content type of the request body. - required: true - - in: path - name: uuid - schema: - type: string - format: uuid - description: Unique identifier (UUID4) - required: true - tags: - - objects - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Object' - required: true - security: - - tokenAuth: [] - responses: - '200': - headers: - Content-Crs: - schema: - type: string - enum: - - EPSG:4326 - description: 'The ''Coordinate Reference System'' (CRS) of the request - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 - is the same as WGS84).' - content: - application/json: - schema: - $ref: '#/components/schemas/Object' - description: OK - patch: - operationId: object_partial_update - description: Update the OBJECT by creating a new RECORD with the updates values. - The provided `record.data` value will be merged recursively with the existing - record data. - parameters: - - in: header - name: Accept-Crs - schema: - type: string - enum: - - EPSG:4326 - description: 'The desired ''Coordinate Reference System'' (CRS) of the response - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 is - the same as WGS84).' - - in: header - name: Content-Crs - schema: - type: string - enum: - - EPSG:4326 - description: 'The ''Coordinate Reference System'' (CRS) of the request data. - According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 is the same - as WGS84).' - required: true - - in: header - name: Content-Type - schema: - type: string - enum: - - application/json - description: Content type of the request body. - required: true - - in: path - name: uuid - schema: - type: string - format: uuid - description: Unique identifier (UUID4) - required: true - tags: - - objects - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/PatchedObject' - security: - - tokenAuth: [] - responses: - '200': - headers: - Content-Crs: - schema: - type: string - enum: - - EPSG:4326 - description: 'The ''Coordinate Reference System'' (CRS) of the request - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 - is the same as WGS84).' - content: - application/json: - schema: - $ref: '#/components/schemas/Object' - description: OK - delete: - operationId: object_delete - description: Delete an OBJECT and all RECORDs belonging to it. - parameters: - - in: path - name: uuid - schema: - type: string - format: uuid - description: Unique identifier (UUID4) - required: true - tags: - - objects - security: - - tokenAuth: [] - responses: - '204': - description: No response body - /objects/{uuid}/history: - get: - operationId: object_history - description: Retrieve all RECORDs of an OBJECT. - parameters: - - in: header - name: Accept-Crs - schema: - type: string - enum: - - EPSG:4326 - description: 'The desired ''Coordinate Reference System'' (CRS) of the response - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 is - the same as WGS84).' - - in: path - name: uuid - schema: - type: string - format: uuid - description: Unique identifier (UUID4) - required: true - tags: - - objects - security: - - tokenAuth: [] - responses: - '200': - headers: - Content-Crs: - schema: - type: string - enum: - - EPSG:4326 - description: 'The ''Coordinate Reference System'' (CRS) of the request - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 - is the same as WGS84).' - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/HistoryRecord' - description: OK - /objects/search: - post: - operationId: object_search - description: Perform a (geo) search on OBJECTs. - parameters: - - in: header - name: Accept-Crs - schema: - type: string - enum: - - EPSG:4326 - description: 'The desired ''Coordinate Reference System'' (CRS) of the response - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 is - the same as WGS84).' - - in: header - name: Content-Crs - schema: - type: string - enum: - - EPSG:4326 - description: 'The ''Coordinate Reference System'' (CRS) of the request data. - According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 is the same - as WGS84).' - required: true - - in: header - name: Content-Type - schema: - type: string - enum: - - application/json - description: Content type of the request body. - required: true - tags: - - objects - requestBody: - content: - application/json: - schema: - type: object - allOf: - - $ref: '#/components/schemas/ObjectSearch' - - type: object - properties: - type: - type: string - format: uri - maxLength: 1000 - minLength: 1 - description: Url reference to OBJECTTYPE in Objecttypes API - data_attrs: - type: string - description: | - Only include objects that have attributes with certain values. - Data filtering expressions are comma-separated and are structured as follows: - A valid parameter value has the form `key__operator__value`. - `key` is the attribute name, `operator` is the comparison operator to be used and `value` is the attribute value. - Note: Values can be string, numeric, or dates (ISO format; YYYY-MM-DD). - - Valid operator values are: - * `exact` - equal to - * `gt` - greater than - * `gte` - greater than or equal to - * `lt` - lower than - * `lte` - lower than or equal to - * `icontains` - case-insensitive partial match - * `in` - in a list of values separated by `|` - - `value` may not contain double underscore or comma characters. - `key` may not contain comma characters and includes double underscore only if it indicates nested attributes. - - Example: in order to display only objects with `height` equal to 100, query `data_attrs=height__exact__100` - should be used. If `height` is nested inside `dimensions` attribute, query should look like - `data_attrs=dimensions__height__exact__100` - date: - type: string - format: date - description: Display record data for the specified material date, - i.e. the specified date would be between `startAt` and `endAt` - attributes. The default value is today - registrationDate: - type: string - format: date - description: Display record data for the specified registration - date, i.e. the specified date would be between `registrationAt` - attributes of different records - security: - - tokenAuth: [] - responses: - '200': - headers: - Content-Crs: - schema: - type: string - enum: - - EPSG:4326 - description: 'The ''Coordinate Reference System'' (CRS) of the request - data. According to the GeoJSON spec, WGS84 is the default (EPSG: 4326 - is the same as WGS84).' - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Object' - description: OK -components: - schemas: - GeoJSONGeometry: - oneOf: - - $ref: '#/components/schemas/Point' - - $ref: '#/components/schemas/MultiPoint' - - $ref: '#/components/schemas/LineString' - - $ref: '#/components/schemas/MultiLineString' - - $ref: '#/components/schemas/Polygon' - - $ref: '#/components/schemas/MultiPolygon' - - $ref: '#/components/schemas/GeometryCollection' - discriminator: - propertyName: type - mapping: - Point: '#/components/schemas/Point' - MultiPoint: '#/components/schemas/MultiPoint' - LineString: '#/components/schemas/LineString' - MultiLineString: '#/components/schemas/MultiLineString' - Polygon: '#/components/schemas/Polygon' - MultiPolygon: '#/components/schemas/MultiPolygon' - GeometryCollection: '#/components/schemas/GeometryCollection' - GeoWithin: - type: object - properties: - within: - $ref: '#/components/schemas/GeoJSONGeometry' - Geometry: - type: object - title: Geometry - description: GeoJSON geometry - required: - - type - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1 - properties: - type: - type: string - description: The geometry type - GeometryCollection: - type: object - description: GeoJSON geometry collection - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.8 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - geometries - properties: - geometries: - type: array - items: - $ref: '#/components/schemas/Geometry' - HistoryRecord: - type: object - properties: - index: - type: integer - readOnly: true - description: Incremental index number of the object record. - typeVersion: - type: integer - maximum: 32767 - minimum: 0 - description: Version of the OBJECTTYPE for data in the object record - data: - description: Object data, based on OBJECTTYPE - geometry: - allOf: - - $ref: '#/components/schemas/GeoJSONGeometry' - nullable: true - description: Point, linestring or polygon object which represents the coordinates - of the object. Geometry can be added only if the related OBJECTTYPE allows - this (`OBJECTTYPE.allowGeometry = true` or `OBJECTTYPE.allowGeometry` - doesn't exist) - startAt: - type: string - format: date - description: Legal start date of the object record - endAt: - type: string - format: date - readOnly: true - nullable: true - description: Legal end date of the object record - registrationAt: - type: string - format: date - readOnly: true - description: The date when the record was registered in the system - correctionFor: - type: integer - maximum: 2147483647 - minimum: 0 - description: Index of the record corrected by the current record - readOnly: true - correctedBy: - type: integer - maximum: 2147483647 - minimum: 0 - description: Index of the record, which corrects the current record - readOnly: true - required: - - startAt - - typeVersion - LineString: - type: object - description: GeoJSON line-string geometry - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.4 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - coordinates - properties: - coordinates: - type: array - items: - $ref: '#/components/schemas/Point2D' - minItems: 2 - MultiLineString: - type: object - description: GeoJSON multi-line-string geometry - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.5 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - coordinates - properties: - coordinates: - type: array - items: - type: array - items: - $ref: '#/components/schemas/Point2D' - MultiPoint: - type: object - description: GeoJSON multi-point geometry - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.3 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - coordinates - properties: - coordinates: - type: array - items: - $ref: '#/components/schemas/Point2D' - MultiPolygon: - type: object - description: GeoJSON multi-polygon geometry - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.7 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - coordinates - properties: - coordinates: - type: array - items: - type: array - items: - type: array - items: - $ref: '#/components/schemas/Point2D' - Object: - type: object - description: |- - this mixin allows selecting fields for serializer in the query param - It also supports nested fields. - properties: - url: - type: string - format: uri - minLength: 1 - maxLength: 1000 - description: URL reference to this object. This is the unique identification - and location of this object. - readOnly: true - uuid: - type: string - format: uuid - description: Unique identifier (UUID4) - type: - type: string - format: uri - minLength: 1 - maxLength: 1000 - description: Url reference to OBJECTTYPE in Objecttypes API - record: - allOf: - - $ref: '#/components/schemas/ObjectRecord' - description: State of the OBJECT at a certain time - required: - - record - - type - ObjectRecord: - type: object - properties: - index: - type: integer - readOnly: true - description: Incremental index number of the object record. - typeVersion: - type: integer - maximum: 32767 - minimum: 0 - description: Version of the OBJECTTYPE for data in the object record - data: - description: Object data, based on OBJECTTYPE - geometry: - allOf: - - $ref: '#/components/schemas/GeoJSONGeometry' - nullable: true - description: Point, linestring or polygon object which represents the coordinates - of the object. Geometry can be added only if the related OBJECTTYPE allows - this (`OBJECTTYPE.allowGeometry = true` or `OBJECTTYPE.allowGeometry` - doesn't exist) - startAt: - type: string - format: date - description: Legal start date of the object record - endAt: - type: string - format: date - readOnly: true - nullable: true - description: Legal end date of the object record - registrationAt: - type: string - format: date - readOnly: true - description: The date when the record was registered in the system - correctionFor: - type: integer - maximum: 2147483647 - minimum: 0 - description: Index of the record corrected by the current record - nullable: true - correctedBy: - type: integer - maximum: 2147483647 - minimum: 0 - description: Index of the record, which corrects the current record - readOnly: true - required: - - startAt - - typeVersion - ObjectSearch: - type: object - properties: - geometry: - $ref: '#/components/schemas/GeoWithin' - PatchedObject: - type: object - description: |- - this mixin allows selecting fields for serializer in the query param - It also supports nested fields. - properties: - url: - type: string - format: uri - minLength: 1 - maxLength: 1000 - description: URL reference to this object. This is the unique identification - and location of this object. - readOnly: true - uuid: - type: string - format: uuid - description: Unique identifier (UUID4) - type: - type: string - format: uri - minLength: 1 - maxLength: 1000 - description: Url reference to OBJECTTYPE in Objecttypes API - record: - allOf: - - $ref: '#/components/schemas/ObjectRecord' - description: State of the OBJECT at a certain time - Point: - type: object - description: GeoJSON point geometry - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.2 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - coordinates - properties: - coordinates: - $ref: '#/components/schemas/Point2D' - Point2D: - type: array - title: Point2D - description: A 2D point - items: - type: number - maxItems: 2 - minItems: 2 - Polygon: - type: object - description: GeoJSON polygon geometry - externalDocs: - url: https://tools.ietf.org/html/rfc7946#section-3.1.6 - allOf: - - $ref: '#/components/schemas/Geometry' - - type: object - required: - - coordinates - properties: - coordinates: - type: array - items: - type: array - items: - $ref: '#/components/schemas/Point2D' - securitySchemes: - tokenAuth: - type: apiKey - in: header - name: Authorization - description: Token-based authentication with required prefix "Token" -tags: -- name: objects -- name: permissions -externalDocs: - url: https://objects-and-objecttypes-api.readthedocs.io/ -servers: -- url: /api/v1 diff --git a/src/objects/api/v1/urls.py b/src/objects/api/v1/urls.py deleted file mode 100644 index 8282de30..00000000 --- a/src/objects/api/v1/urls.py +++ /dev/null @@ -1,39 +0,0 @@ -from django.urls import include, path - -from drf_spectacular.views import ( - SpectacularJSONAPIView, - SpectacularRedocView, - SpectacularYAMLAPIView, -) -from rest_framework import routers - -from .views import ObjectViewSet - -router = routers.DefaultRouter(trailing_slash=False) -router.register(r"objects", ObjectViewSet, basename="object") - -app_name = "v1" - -urlpatterns = [ - path("", SpectacularJSONAPIView.as_view(), name="schema-json"), - path( - "/", - include( - [ - # schema - path( - "schema/openapi.yaml", - SpectacularYAMLAPIView.as_view(), - name="schema", - ), - path( - "schema/", - SpectacularRedocView.as_view(url_name="schema"), - name="schema-redoc", - ), - # actual endpoints - path("", include(router.urls)), - ] - ), - ), -] diff --git a/src/objects/api/v1/views.py b/src/objects/api/v1/views.py deleted file mode 100644 index 85a0f265..00000000 --- a/src/objects/api/v1/views.py +++ /dev/null @@ -1,159 +0,0 @@ -import datetime - -from django.conf import settings -from django.db import models - -from drf_spectacular.utils import extend_schema, extend_schema_view -from rest_framework import viewsets -from rest_framework.decorators import action -from rest_framework.response import Response -from vng_api_common.search import SearchMixin - -from objects.core.models import ObjectRecord -from objects.token.models import Permission -from objects.token.permissions import ObjectTypeBasedPermission - -from ..kanalen import KANAAL_OBJECTEN -from ..mixins import GeoMixin, ObjectNotificationMixin -from ..serializers import ( - HistoryRecordSerializer, - ObjectSearchSerializer, - ObjectSerializer, -) -from .filters import ObjectRecordFilterSet - - -@extend_schema_view( - list=extend_schema( - description="Retrieve a list of OBJECTs and their actual RECORD. " - "The actual record is defined as if the query parameter `date=` was given." - ), - retrieve=extend_schema( - description="Retrieve a single OBJECT and its actual RECORD. " - "The actual record is defined as if the query parameter `date=` was given.", - operation_id="object_read", - ), - create=extend_schema(description="Create an OBJECT and its initial RECORD."), - update=extend_schema( - description="Update the OBJECT by creating a new RECORD with the updates values." - ), - partial_update=extend_schema( - description="Update the OBJECT by creating a new RECORD with the updates values. " - "The provided `record.data` value will be merged recursively with the existing record data." - ), - destroy=extend_schema( - description="Delete an OBJECT and all RECORDs belonging to it.", - operation_id="object_delete", - ), -) -class ObjectViewSet( - ObjectNotificationMixin, SearchMixin, GeoMixin, viewsets.ModelViewSet -): - queryset = ObjectRecord.objects.select_related( - "object", - "object__object_type", - "object__object_type__service", - "correct", - "corrected", - ).order_by("-pk") - serializer_class = ObjectSerializer - filterset_class = ObjectRecordFilterSet - lookup_field = "object__uuid" - lookup_url_kwarg = "uuid" - search_input_serializer_class = ObjectSearchSerializer - permission_classes = [ObjectTypeBasedPermission] - notifications_kanaal = KANAAL_OBJECTEN - - def get_queryset(self): - base = super().get_queryset() - token_auth = getattr(self.request, "auth", None) - # prefetch permissions for DB optimization. Used in DynamicFieldsMixin - base = base.prefetch_related( - models.Prefetch( - "object__object_type__permissions", - queryset=Permission.objects.filter(token_auth=token_auth), - to_attr="token_permissions", - ), - ) - - if self.action not in ("list", "search"): - return base - - # show only allowed objects - base = base.filter_for_token(token_auth) - - # show only actual objects - date = getattr(self.request, "query_params", {}).get("date", None) - registration_date = getattr(self.request, "query_params", {}).get( - "registrationDate", None - ) - if not date and not registration_date: - base = base.filter_for_date(datetime.date.today()) - - return base - - def filter_queryset(self, queryset): - queryset = super().filter_queryset(queryset) - - # keep only records with max index per object - return queryset.keep_max_record_per_object() - - def perform_destroy(self, instance): - instance.object.delete() - - @extend_schema( - description="Retrieve all RECORDs of an OBJECT.", - responses={"200": HistoryRecordSerializer(many=True)}, - ) - @action(detail=True, methods=["get"], serializer_class=HistoryRecordSerializer) - def history(self, request, uuid=None): - """Retrieve all RECORDs of an OBJECT.""" - records = self.get_object().object.records.order_by("id") - serializer = self.get_serializer(records, many=True) - return Response(serializer.data) - - @extend_schema( - description="Perform a (geo) search on OBJECTs.", - request=ObjectSearchSerializer, - responses={"200": ObjectSerializer(many=True)}, - ) - @action(detail=False, methods=["post"]) - def search(self, request): - """Perform a (geo) search on OBJECTs""" - search_input = self.get_search_input() - queryset = self.filter_queryset(self.get_queryset()) - - if "geometry" in search_input: - within = search_input["geometry"]["within"] - queryset = queryset.filter(geometry__within=within).distinct() - - return self.get_search_output(queryset) - - def get_search_output(self, queryset: models.QuerySet) -> Response: - """wrapper to make sure the result is a Response subclass""" - result = super().get_search_output(queryset) - - if not isinstance(result, Response): - result = Response(result) - - return result - - # for OAS generation - search.is_search_action = True - - def finalize_response(self, request, response, *args, **kwargs): - """add warning header if not all data is allowed to display""" - serializer = getattr(response.data, "serializer", None) - - if serializer and response.status_code == 200: - if self.action == "retrieve" and serializer.not_allowed: - self.headers[settings.UNAUTHORIZED_FIELDS_HEADER] = ( - serializer.not_allowed.pretty() - ) - - elif self.action in ("list", "search") and serializer.child.not_allowed: - self.headers[settings.UNAUTHORIZED_FIELDS_HEADER] = ( - serializer.child.not_allowed.pretty() - ) - - return super().finalize_response(request, response, *args, **kwargs) diff --git a/src/objects/api/v2/openapi.yaml b/src/objects/api/v2/openapi.yaml index 125a9b28..8dc2885b 100644 --- a/src/objects/api/v2/openapi.yaml +++ b/src/objects/api/v2/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: Objects API - version: 2.4.3 (v2) + version: 2.4.3 description: | An API to manage Objects. @@ -1148,10 +1148,10 @@ components: in: header name: Authorization description: Token-based authentication with required prefix "Token" +servers: +- url: /api/v2 tags: - name: objects - name: permissions externalDocs: url: https://objects-and-objecttypes-api.readthedocs.io/ -servers: -- url: /api/v2 diff --git a/src/objects/conf/api.py b/src/objects/conf/api.py index c8b72a93..0a40a12b 100644 --- a/src/objects/conf/api.py +++ b/src/objects/conf/api.py @@ -1,5 +1,4 @@ API_VERSION = "2.4.3" -VERSIONS = {"v1": "1.3.0", "v2": "2.4.3"} # api settings REST_FRAMEWORK = { @@ -12,7 +11,7 @@ "DEFAULT_SCHEMA_CLASS": "objects.utils.autoschema.AutoSchema", "DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.NamespaceVersioning", "DEFAULT_VERSION": "v2", # NOT to be confused with API_VERSION - it's the major version part - "ALLOWED_VERSIONS": ("v1", "v2"), + "ALLOWED_VERSIONS": ("v2",), "VERSION_PARAM": "version", "EXCEPTION_HANDLER": "objects.utils.views.exception_handler", # test @@ -99,16 +98,13 @@ "EXTERNAL_DOCS": { "url": "https://objects-and-objecttypes-api.readthedocs.io/", }, - "VERSION": None, + "VERSION": API_VERSION, "COMPONENT_NO_READ_ONLY_REQUIRED": True, "POSTPROCESSING_HOOKS": [ "drf_spectacular.hooks.postprocess_schema_enums", - "objects.utils.hooks.postprocess_servers", - "objects.utils.hooks.postprocess_versions", ], "TAGS": [{"name": "objects"}, {"name": "permissions"}], + "SERVERS": [{"url": "/api/v2"}], } -OAS_SERVERS = {"v1": [{"url": "/api/v1"}], "v2": [{"url": "/api/v2"}]} - UNAUTHORIZED_FIELDS_HEADER = "X-Unauthorized-Fields" diff --git a/src/objects/tests/v1/__init__.py b/src/objects/tests/v1/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/objects/tests/v1/test_auth.py b/src/objects/tests/v1/test_auth.py deleted file mode 100644 index 17e57dd1..00000000 --- a/src/objects/tests/v1/test_auth.py +++ /dev/null @@ -1,294 +0,0 @@ -from django.contrib.gis.geos import Point - -from rest_framework import status -from rest_framework.test import APITestCase - -from objects.core.tests.factories import ( - ObjectFactory, - ObjectRecordFactory, - ObjectTypeFactory, -) -from objects.token.constants import PermissionModes -from objects.token.tests.factories import PermissionFactory, TokenAuthFactory -from objects.utils.test import TokenAuthMixin - -from ..constants import GEO_WRITE_KWARGS, POLYGON_AMSTERDAM_CENTRUM -from .utils import reverse, reverse_lazy - -OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" - - -class TokenAuthTests(APITestCase): - def setUp(self) -> None: - object = ObjectFactory.create() - self.urls = [ - reverse("object-list"), - reverse("object-detail", args=[object.uuid]), - ] - - def test_non_auth(self): - for url in self.urls: - with self.subTest(url=url): - response = self.client.get(url) - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - def test_invalid_token(self): - TokenAuthFactory.create() - for url in self.urls: - with self.subTest(url=url): - response = self.client.get(url, HTTP_AUTHORIZATION="Token 12345") - self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) - - -class PermissionTests(TokenAuthMixin, APITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - - def test_retrieve_no_object_permission(self): - object = ObjectFactory.create() - ObjectRecordFactory.create(object=object) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_retrieve_with_read_only_permission(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - ) - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=object) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_history_no_object_permissions(self): - object = ObjectFactory.create() - ObjectRecordFactory.create(object=object) - url = reverse("object-history", args=[object.uuid]) - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_history_with_read_only_permissions(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - ) - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=object) - url = reverse("object-history", args=[object.uuid]) - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - def test_history_with_fields_permissions(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields=["url"], - ) - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=object) - url = reverse("object-history", args=[object.uuid]) - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_update_with_read_only_perm(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - ) - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=object) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.put(url, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_patch_with_read_only_perm(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - ) - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=object) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.patch(url, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_destroy_with_read_only_perm(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - ) - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=object) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.delete(url, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_create_with_invalid_objecttype(self): - url = reverse("object-list") - data = { - "type": "invalid-objecttype-url", - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12"}, - "startDate": "2020-01-01", - }, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_create_with_unknown_objecttype_service(self): - url = reverse("object-list") - data = { - "type": "https://other-api.nl/v1/objecttypes/8be76be2-6567-4f5c-a17b-05217ab6d7b2", - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12"}, - "startDate": "2020-01-01", - }, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - def test_create_with_unknown_objecttype_uuid(self): - url = reverse("object-list") - data = { - "type": f"{OBJECT_TYPES_API}objecttypes/8be76be2-6567-4f5c-a17b-05217ab6d7b2", - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12"}, - "startDate": "2020-01-01", - }, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - - -class FilterAuthTests(TokenAuthMixin, APITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - - def test_list_objects_without_object_permissions(self): - ObjectFactory.create_batch(2) - url = reverse_lazy("object-list") - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.json()), 0) - - def test_list_objects_limited_to_object_permission(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - ) - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=object) - ObjectFactory.create() - url = reverse_lazy("object-list") - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[object.uuid])}", - ) - - def test_search_objects_without_object_permissions(self): - ObjectRecordFactory.create_batch(2, geometry=Point(4.905289, 52.369918)) - url = reverse("object-search") - - response = self.client.post( - url, - { - "geometry": { - "within": { - "type": "Polygon", - "coordinates": [POLYGON_AMSTERDAM_CENTRUM], - } - }, - "type": self.object_type.url, - }, - **GEO_WRITE_KWARGS, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(len(response.json()), 0) - - def test_search_objects_limited_to_object_permission(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - ) - record = ObjectRecordFactory.create( - geometry=Point(4.905289, 52.369918), object__object_type=self.object_type - ) - ObjectRecordFactory.create(geometry=Point(4.905289, 52.369918)) - url = reverse("object-search") - - response = self.client.post( - url, - { - "geometry": { - "within": { - "type": "Polygon", - "coordinates": [POLYGON_AMSTERDAM_CENTRUM], - } - }, - "type": self.object_type.url, - }, - **GEO_WRITE_KWARGS, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[record.object.uuid])}", - ) diff --git a/src/objects/tests/v1/test_auth_fields.py b/src/objects/tests/v1/test_auth_fields.py deleted file mode 100644 index 8fc09344..00000000 --- a/src/objects/tests/v1/test_auth_fields.py +++ /dev/null @@ -1,433 +0,0 @@ -import json - -from django.contrib.gis.geos import Point - -from rest_framework import status -from rest_framework.test import APITestCase - -from objects.core.tests.factories import ( - ObjectFactory, - ObjectRecordFactory, - ObjectTypeFactory, -) -from objects.token.constants import PermissionModes -from objects.token.tests.factories import PermissionFactory -from objects.utils.test import TokenAuthMixin - -from ..constants import GEO_WRITE_KWARGS, POLYGON_AMSTERDAM_CENTRUM -from .utils import reverse, reverse_lazy - -OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" - - -class RetrieveAuthFieldsTests(TokenAuthMixin, APITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - - def test_retrieve_without_query(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "type", "record__startAt"]}, - ) - object = ObjectFactory.create(object_type=self.object_type) - record = ObjectRecordFactory.create( - object=object, data={"name": "some"}, version=1 - ) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - { - "url": f"http://testserver{url}", - "type": self.object_type.url, - "record": {"startAt": record.start_at.isoformat()}, - }, - ) - self.assertEqual( - set(response.headers["x-unauthorized-fields"].split(",")), - { - "uuid", - "record__data__name", - "record__correctionFor", - "record__endAt", - "record__correctedBy", - "record__registrationAt", - "record__index", - "record__geometry__coordinates", - "record__geometry__type", - "record__typeVersion", - }, - ) - - def test_retrieve_with_query_fields(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "type", "record"]}, - ) - object = ObjectFactory.create(object_type=self.object_type) - record = ObjectRecordFactory.create(object=object, version=1) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.get(url, {"fields": "url,type,record__data__name"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - { - "url": f"http://testserver{url}", - "type": self.object_type.url, - "record": {"data": {"name": record.data["name"]}}, - }, - ) - self.assertNotIn("x-unauthorized-fields", response.headers) - - def test_retrieve_incorrect_auth_fields(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "type", "some"]}, - ) - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=object, version=1) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - ["Fields in the configured authorization are absent in the data: 'some'"], - ) - - def test_retrieve_query_fields_not_allowed(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "type", "record__data__name"]}, - ) - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create( - object=object, data={"name": "some", "desc": "some desc"}, version=1 - ) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.get(url, {"fields": "url,type,record__data"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - { - "url": f"http://testserver{url}", - "record": {"data": {"name": "some"}}, - "type": self.object_type.url, - }, - ) - self.assertEqual( - response.headers["x-unauthorized-fields"], "record__data__desc" - ) - - def test_retrieve_no_allowed_fields(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"2": ["url", "type", "record"]}, - ) - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create( - object=object, data={"name": "some", "desc": "some desc"}, version=1 - ) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json(), {}) - self.assertIn("x-unauthorized-fields", response.headers) - - -class ListAuthFieldsTests(TokenAuthMixin, APITestCase): - url = reverse_lazy("object-list") - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - cls.other_object_type = ObjectTypeFactory() - - def test_list_without_query_different_object_types(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "type", "record"]}, - ) - PermissionFactory.create( - object_type=self.other_object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "uuid", "record"]}, - ) - record1 = ObjectRecordFactory.create( - object__object_type=self.object_type, data={"name": "some"}, version=1 - ) - record2 = ObjectRecordFactory.create( - object__object_type=self.other_object_type, - data={"name": "other"}, - version=1, - ) - - response = self.client.get(self.url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - [ - { - "url": f"http://testserver{reverse('object-detail', args=[record2.object.uuid])}", - "uuid": str(record2.object.uuid), - "record": { - "index": record2.index, - "typeVersion": record2.version, - "data": {"name": "other"}, - "geometry": json.loads(record2.geometry.json), - "startAt": record2.start_at.isoformat(), - "endAt": None, - "registrationAt": record2.registration_at.isoformat(), - "correctionFor": None, - "correctedBy": None, - }, - }, - { - "url": f"http://testserver{reverse('object-detail', args=[record1.object.uuid])}", - "type": self.object_type.url, - "record": { - "index": record1.index, - "typeVersion": record1.version, - "data": {"name": "some"}, - "geometry": json.loads(record1.geometry.json), - "startAt": record1.start_at.isoformat(), - "endAt": None, - "registrationAt": record1.registration_at.isoformat(), - "correctionFor": None, - "correctedBy": None, - }, - }, - ], - ) - self.assertEqual( - response.headers["x-unauthorized-fields"], - f"{self.other_object_type.url}(1)=type; {self.object_type.url}(1)=uuid", - ) - - def test_list_with_query_fields(self): - other_object_type = ObjectTypeFactory() - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "type", "record"]}, - ) - PermissionFactory.create( - object_type=other_object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "uuid", "record"]}, - ) - record1 = ObjectRecordFactory.create( - object__object_type=self.object_type, data={"name": "some"}, version=1 - ) - record2 = ObjectRecordFactory.create( - object__object_type=other_object_type, data={"name": "other"}, version=1 - ) - - response = self.client.get(self.url, {"fields": "url,record__data"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - [ - { - "url": f"http://testserver{reverse('object-detail', args=[record2.object.uuid])}", - "record": { - "data": {"name": "other"}, - }, - }, - { - "url": f"http://testserver{reverse('object-detail', args=[record1.object.uuid])}", - "record": { - "data": {"name": "some"}, - }, - }, - ], - ) - self.assertNotIn("x-unauthorized-fields", response.headers) - - def test_list_incorrect_auth_fields(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "some", "record"]}, - ) - PermissionFactory.create( - object_type=self.other_object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "uuid", "record"]}, - ) - ObjectRecordFactory.create( - object__object_type=self.object_type, data={"name": "some"}, version=1 - ) - ObjectRecordFactory.create( - object__object_type=self.other_object_type, - data={"name": "other"}, - version=1, - ) - - response = self.client.get(self.url) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - ["Fields in the configured authorization are absent in the data: 'some'"], - ) - - def test_list_query_fields_not_allowed(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "type", "record"]}, - ) - PermissionFactory.create( - object_type=self.other_object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "uuid", "record"]}, - ) - ObjectRecordFactory.create( - object__object_type=self.object_type, data={"name": "some"} - ) - ObjectRecordFactory.create( - object__object_type=self.other_object_type, data={"name": "other"} - ) - - response = self.client.get(self.url, {"fields": "uuid"}) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - ["'fields' query parameter has invalid or unauthorized values: 'uuid'"], - ) - - def test_list_no_allowed_fields(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"2": ["url", "type", "record"]}, - ) - PermissionFactory.create( - object_type=self.other_object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"2": ["url", "uuid", "record"]}, - ) - ObjectRecordFactory.create( - object__object_type=self.object_type, data={"name": "some"}, version=1 - ) - ObjectRecordFactory.create( - object__object_type=self.other_object_type, - data={"name": "other"}, - version=1, - ) - url = reverse("object-list") - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json(), [{}, {}]) - self.assertIn("x-unauthorized-fields", response.headers) - - -class SearchAuthFieldsTests(TokenAuthMixin, APITestCase): - url = reverse_lazy("object-search") - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - - def test_search_with_fields_auth(self): - PermissionFactory.create( - object_type=self.object_type, - mode=PermissionModes.read_only, - token_auth=self.token_auth, - use_fields=True, - fields={"1": ["url", "type", "record__geometry"]}, - ) - record = ObjectRecordFactory.create( - geometry=Point(4.905289, 52.369918), - object__object_type=self.object_type, - data={"name": "some"}, - version=1, - ) - response = self.client.post( - self.url, - data={ - "geometry": { - "within": { - "type": "Polygon", - "coordinates": [POLYGON_AMSTERDAM_CENTRUM], - } - } - }, - **GEO_WRITE_KWARGS, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual( - response.json(), - [ - { - "url": f"http://testserver{reverse('object-detail', args=[record.object.uuid])}", - "type": self.object_type.url, - "record": { - "geometry": { - "type": "Point", - "coordinates": [4.905289, 52.369918], - } - }, - } - ], - ) diff --git a/src/objects/tests/v1/test_filters.py b/src/objects/tests/v1/test_filters.py deleted file mode 100644 index 5c74a3c5..00000000 --- a/src/objects/tests/v1/test_filters.py +++ /dev/null @@ -1,491 +0,0 @@ -from datetime import date, timedelta - -from rest_framework import status -from rest_framework.test import APITestCase - -from objects.core.tests.factories import ( - ObjectFactory, - ObjectRecordFactory, - ObjectTypeFactory, -) -from objects.token.constants import PermissionModes -from objects.token.tests.factories import PermissionFactory -from objects.utils.test import TokenAuthMixin - -from .utils import reverse, reverse_lazy - -OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" - - -class FilterObjectTypeTests(TokenAuthMixin, APITestCase): - url = reverse_lazy("object-list") - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - cls.another_object_type = ObjectTypeFactory(service=cls.object_type.service) - - PermissionFactory.create( - object_type=cls.object_type, - mode=PermissionModes.read_only, - token_auth=cls.token_auth, - ) - PermissionFactory.create( - object_type=cls.another_object_type, - mode=PermissionModes.read_only, - token_auth=cls.token_auth, - ) - - def test_filter_object_type(self): - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=object) - ObjectFactory.create(object_type=self.another_object_type) - - response = self.client.get(self.url, {"type": self.object_type.url}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[object.uuid])}", - ) - - def test_filter_invalid_objecttype(self): - response = self.client.get(self.url, {"type": "invalid-objecttype-url"}) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json()["type"], ["Invalid value."]) - - def test_filter_unknown_objecttype(self): - objecttype_url = ( - f"{OBJECT_TYPES_API}objecttypes/8be76be2-6567-4f5c-a17b-05217ab6d7b2" - ) - response = self.client.get(self.url, {"type": objecttype_url}) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json()["type"], - [ - f"Select a valid object type. {objecttype_url} is not one of the available choices." - ], - ) - - def test_filter_too_long_object_type(self): - object_type_long = f"{OBJECT_TYPES_API}{'a'*1000}/{self.object_type.uuid}" - response = self.client.get(self.url, {"type": object_type_long}) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json()["type"], ["The value has too many characters"]) - - -class FilterDataAttrsTests(TokenAuthMixin, APITestCase): - url = reverse_lazy("object-list") - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - PermissionFactory.create( - object_type=cls.object_type, - mode=PermissionModes.read_only, - token_auth=cls.token_auth, - ) - - def test_filter_exact_string(self): - record = ObjectRecordFactory.create( - data={"name": "demo"}, object__object_type=self.object_type - ) - ObjectRecordFactory.create( - data={"name": "demo2"}, object__object_type=self.object_type - ) - ObjectRecordFactory.create(data={}, object__object_type=self.object_type) - - response = self.client.get(self.url, {"data_attrs": "name__exact__demo"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[record.object.uuid])}", - ) - - def test_filter_exact_number(self): - record = ObjectRecordFactory.create( - data={"diameter": 4}, object__object_type=self.object_type - ) - ObjectRecordFactory.create( - data={"diameter": 6}, object__object_type=self.object_type - ) - ObjectRecordFactory.create(data={}, object__object_type=self.object_type) - - response = self.client.get(self.url, {"data_attrs": "diameter__exact__4"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[record.object.uuid])}", - ) - - def test_filter_lte(self): - record1 = ObjectRecordFactory.create( - data={"diameter": 4}, object__object_type=self.object_type - ) - record2 = ObjectRecordFactory.create( - data={"diameter": 5}, object__object_type=self.object_type - ) - ObjectRecordFactory.create( - data={"diameter": 6}, object__object_type=self.object_type - ) - ObjectRecordFactory.create(data={}, object__object_type=self.object_type) - - response = self.client.get(self.url, {"data_attrs": "diameter__lte__5"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - data = sorted(data, key=lambda x: x["record"]["data"]["diameter"]) - - self.assertEqual(len(data), 2) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[record1.object.uuid])}", - ) - self.assertEqual( - data[1]["url"], - f"http://testserver{reverse('object-detail', args=[record2.object.uuid])}", - ) - - def test_filter_lt(self): - record = ObjectRecordFactory.create( - data={"diameter": 4}, object__object_type=self.object_type - ) - ObjectRecordFactory.create( - data={"diameter": 5}, object__object_type=self.object_type - ) - ObjectRecordFactory.create( - data={"diameter": 6}, object__object_type=self.object_type - ) - ObjectRecordFactory.create(data={}, object__object_type=self.object_type) - - response = self.client.get(self.url, {"data_attrs": "diameter__lt__5"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[record.object.uuid])}", - ) - - def test_filter_lte_not_numerical(self): - response = self.client.get(self.url, {"data_attrs": "diameter__lt__value"}) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), ["Operator `lt` supports only dates and/or numeric values"] - ) - - def test_filter_invalid_operator(self): - response = self.client.get(self.url, {"data_attrs": "diameter__not__value"}) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.json(), ["Comparison operator `not` is unknown"]) - - def test_filter_invalid_param(self): - response = self.client.get(self.url, {"data_attrs": "diameter__exact"}) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), ["not enough values to unpack (expected 3, got 2)"] - ) - - def test_filter_nested_attr(self): - record = ObjectRecordFactory.create( - data={"dimensions": {"diameter": 4}}, object__object_type=self.object_type - ) - ObjectRecordFactory.create( - data={"dimensions": {"diameter": 5}}, object__object_type=self.object_type - ) - ObjectRecordFactory.create( - data={"diameter": 4}, object__object_type=self.object_type - ) - ObjectRecordFactory.create(data={}, object__object_type=self.object_type) - - response = self.client.get( - self.url, {"data_attrs": "dimensions__diameter__exact__4"} - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[record.object.uuid])}", - ) - - def test_filter_comma_separated(self): - record = ObjectRecordFactory.create( - data={"dimensions": {"diameter": 4}, "name": "demo"}, - object__object_type=self.object_type, - ) - ObjectRecordFactory.create( - data={"dimensions": {"diameter": 5}, "name": "demo"}, - object__object_type=self.object_type, - ) - ObjectRecordFactory.create( - data={"dimensions": {"diameter": 4}, "name": "other"}, - object__object_type=self.object_type, - ) - - response = self.client.get( - self.url, {"data_attrs": "dimensions__diameter__exact__4,name__exact__demo"} - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[record.object.uuid])}", - ) - - def test_filter_icontains_string(self): - record = ObjectRecordFactory.create( - data={"name": "Something important"}, object__object_type=self.object_type - ) - ObjectRecordFactory.create( - data={"name": "Nothing important"}, object__object_type=self.object_type - ) - ObjectRecordFactory.create(data={}, object__object_type=self.object_type) - - response = self.client.get(self.url, {"data_attrs": "name__icontains__some"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[record.object.uuid])}", - ) - - def test_filter_icontains_numeric(self): - record = ObjectRecordFactory.create( - data={"diameter": 45}, object__object_type=self.object_type - ) - ObjectRecordFactory.create( - data={"diameter": 6}, object__object_type=self.object_type - ) - ObjectRecordFactory.create(data={}, object__object_type=self.object_type) - - response = self.client.get(self.url, {"data_attrs": "diameter__icontains__4"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[record.object.uuid])}", - ) - - def test_filter_exclude_old_records(self): - record_old = ObjectRecordFactory.create( - data={"diameter": 45}, - object__object_type=self.object_type, - start_at=date.today() - timedelta(days=10), - end_at=date.today() - timedelta(days=1), - ) - record_new = ObjectRecordFactory.create( - data={"diameter": 50}, object=record_old.object, start_at=record_old.end_at - ) - - response = self.client.get(self.url, {"data_attrs": "diameter__exact__45"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - self.assertEqual(len(data), 0) - - def test_filter_in_string(self): - record = ObjectRecordFactory.create( - data={"name": "demo1"}, object__object_type=self.object_type - ) - record2 = ObjectRecordFactory.create( - data={"name": "demo2"}, object__object_type=self.object_type - ) - ObjectRecordFactory.create( - data={"name": "demo3"}, object__object_type=self.object_type - ) - - response = self.client.get(self.url, {"data_attrs": "name__in__demo1|demo2"}) - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - self.assertEqual(len(data), 2) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[record2.object.uuid])}", - ) - self.assertEqual( - data[1]["url"], - f"http://testserver{reverse('object-detail', args=[record.object.uuid])}", - ) - - -class FilterDateTests(TokenAuthMixin, APITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - PermissionFactory.create( - object_type=cls.object_type, - mode=PermissionModes.read_only, - token_auth=cls.token_auth, - ) - - def test_filter_date_detail(self): - object = ObjectFactory.create(object_type=self.object_type) - record1 = ObjectRecordFactory.create( - object=object, start_at="2020-01-01", end_at="2020-12-31" - ) - record2 = ObjectRecordFactory.create(object=object, start_at="2021-01-01") - - url = reverse_lazy("object-detail", args=[object.uuid]) - - response = self.client.get(url, {"date": "2020-07-01"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(data["record"]["index"], record1.index) - - def test_filter_date_detail_no_actual_record(self): - object = ObjectFactory.create(object_type=self.object_type) - record = ObjectRecordFactory.create(object=object, start_at="2021-01-01") - - url = reverse_lazy("object-detail", args=[object.uuid]) - - response = self.client.get(url, {"date": "2020-07-01"}) - - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_filter_date_list(self): - # object 1 - show - object1 = ObjectFactory.create(object_type=self.object_type) - record11 = ObjectRecordFactory.create( - object=object1, start_at="2020-01-01", end_at="2020-12-31" - ) - record12 = ObjectRecordFactory.create(object=object1, start_at="2021-01-01") - # object 2 - don't show - record21 = ObjectRecordFactory.create( - object__object_type=self.object_type, start_at="2021-01-01" - ) - - url = reverse_lazy("object-list") - - response = self.client.get(url, {"date": "2020-07-01"}) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[object1.uuid])}", - ) - self.assertEqual(data[0]["record"]["index"], record11.index) - - def test_filter_registration_date_detail(self): - object = ObjectFactory.create(object_type=self.object_type) - record1 = ObjectRecordFactory.create( - object=object, - registration_at="2020-01-01", - ) - record2 = ObjectRecordFactory.create( - object=object, registration_at="2021-01-01" - ) - - url = reverse_lazy("object-detail", args=[object.uuid]) - - response = self.client.get(url, {"registrationDate": "2020-07-01"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(data["record"]["index"], record1.index) - - def test_filter_registration_date_detail_no_record(self): - object = ObjectFactory.create(object_type=self.object_type) - record = ObjectRecordFactory.create(object=object, registration_at="2021-01-01") - - url = reverse_lazy("object-detail", args=[object.uuid]) - - response = self.client.get(url, {"registrationDate": "2020-07-01"}) - - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_filter_registration_date_list(self): - # object 1 - show - object1 = ObjectFactory.create(object_type=self.object_type) - record11 = ObjectRecordFactory.create( - object=object1, registration_at="2020-01-01" - ) - record12 = ObjectRecordFactory.create( - object=object1, registration_at="2021-01-01" - ) - # object 2 - don't show - record21 = ObjectRecordFactory.create( - object__object_type=self.object_type, registration_at="2021-01-01" - ) - - url = reverse_lazy("object-list") - - response = self.client.get(url, {"registrationDate": "2020-07-01"}) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[object1.uuid])}", - ) - self.assertEqual(data[0]["record"]["index"], record11.index) - - def test_filter_on_both_date_and_registration_date(self): - url = reverse_lazy("object-list") - - response = self.client.get( - url, {"date": "2020-07-01", "registrationDate": "2020-08-01"} - ) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - [ - "'date' and 'registrationDate' parameters can't be used in the same request" - ], - ) diff --git a/src/objects/tests/v1/test_geo_headers.py b/src/objects/tests/v1/test_geo_headers.py deleted file mode 100644 index 3dab9547..00000000 --- a/src/objects/tests/v1/test_geo_headers.py +++ /dev/null @@ -1,124 +0,0 @@ -from rest_framework import status -from rest_framework.test import APITestCase - -from objects.core.tests.factories import ( - ObjectFactory, - ObjectRecordFactory, - ObjectTypeFactory, -) -from objects.token.constants import PermissionModes -from objects.token.tests.factories import PermissionFactory -from objects.utils.test import TokenAuthMixin - -from ..constants import GEO_READ_KWARGS, POLYGON_AMSTERDAM_CENTRUM -from .utils import reverse - -OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" - - -class GeoHeaderTests(TokenAuthMixin, APITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - PermissionFactory.create( - object_type=cls.object_type, - mode=PermissionModes.read_and_write, - token_auth=cls.token_auth, - ) - - def assertResponseHasGeoHeaders(self, response): - self.assertTrue("Content-Crs" in response) - self.assertEqual(response["Content-Crs"], "EPSG:4326") - - def test_get_without_geo_headers(self): - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=object) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertResponseHasGeoHeaders(response) - - def test_get_with_geo_headers(self): - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=object) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.get(url, **GEO_READ_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertResponseHasGeoHeaders(response) - - def test_get_with_incorrect_get_headers(self): - object = ObjectFactory.create(object_type=self.object_type) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.get(url, HTTP_ACCEPT_CRS="EPSG:3857") - - self.assertEqual(response.status_code, status.HTTP_406_NOT_ACCEPTABLE) - - def test_create_without_geo_headers(self): - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 1, - "data": {"diameter": 30}, - "startAt": "2020-01-01", - }, - } - url = reverse("object-list") - - response = self.client.post(url, data) - - self.assertEqual(response.status_code, status.HTTP_412_PRECONDITION_FAILED) - - def test_update_without_geo_headers(self): - object = ObjectFactory.create(object_type=self.object_type) - url = reverse("object-detail", args=[object.uuid]) - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 1, - "data": {"diameter": 30}, - "startAt": "2020-01-01", - }, - } - - for method in ("put", "patch"): - with self.subTest(method=method): - do_request = getattr(self.client, method) - - response = do_request(url, data) - - self.assertEqual( - response.status_code, status.HTTP_412_PRECONDITION_FAILED - ) - - def test_delete_without_geo_headers(self): - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=object) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.delete(url) - - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - - def test_search_without_geo_headers(self): - url = reverse("object-search") - - response = self.client.post( - url, - { - "geometry": { - "within": { - "type": "Polygon", - "coordinates": [POLYGON_AMSTERDAM_CENTRUM], - } - } - }, - ) - - self.assertEqual(response.status_code, status.HTTP_412_PRECONDITION_FAILED) diff --git a/src/objects/tests/v1/test_geo_search.py b/src/objects/tests/v1/test_geo_search.py deleted file mode 100644 index be31bcf9..00000000 --- a/src/objects/tests/v1/test_geo_search.py +++ /dev/null @@ -1,124 +0,0 @@ -from django.contrib.gis.geos import Point - -from rest_framework import status -from rest_framework.test import APITestCase - -from objects.core.tests.factories import ObjectRecordFactory, ObjectTypeFactory -from objects.token.constants import PermissionModes -from objects.token.tests.factories import PermissionFactory -from objects.utils.test import TokenAuthMixin - -from ..constants import GEO_WRITE_KWARGS, POLYGON_AMSTERDAM_CENTRUM -from .utils import reverse, reverse_lazy - -OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" - - -class GeoSearchTests(TokenAuthMixin, APITestCase): - url = reverse_lazy("object-search") - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - cls.another_object_type = ObjectTypeFactory(service=cls.object_type.service) - - PermissionFactory.create( - object_type=cls.object_type, - mode=PermissionModes.read_only, - token_auth=cls.token_auth, - ) - PermissionFactory.create( - object_type=cls.another_object_type, - mode=PermissionModes.read_only, - token_auth=cls.token_auth, - ) - - def test_filter_within(self): - # in district - record = ObjectRecordFactory.create( - object__object_type=self.object_type, geometry=Point(4.905289, 52.369918) - ) - # outside of district - ObjectRecordFactory.create( - object__object_type=self.object_type, geometry=Point(4.905650, 52.357621) - ) - # no geo set - ObjectRecordFactory.create(object__object_type=self.object_type) - - response = self.client.post( - self.url, - { - "geometry": { - "within": { - "type": "Polygon", - "coordinates": [POLYGON_AMSTERDAM_CENTRUM], - } - } - }, - **GEO_WRITE_KWARGS, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f'http://testserver{reverse("object-detail", args=[record.object.uuid])}', - ) - - def test_filter_objecttype(self): - record = ObjectRecordFactory.create( - geometry=Point(4.905289, 52.369918), object__object_type=self.object_type - ) - ObjectRecordFactory.create( - geometry=Point(4.905289, 52.369918), - object__object_type=self.another_object_type, - ) - - response = self.client.post( - self.url, - { - "geometry": { - "within": { - "type": "Polygon", - "coordinates": [POLYGON_AMSTERDAM_CENTRUM], - } - }, - "type": self.object_type.url, - }, - **GEO_WRITE_KWARGS, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f'http://testserver{reverse("object-detail", args=[record.object.uuid])}', - ) - - def test_without_geometry(self): - record = ObjectRecordFactory.create(object__object_type=self.object_type) - ObjectRecordFactory.create(object__object_type=self.another_object_type) - - response = self.client.post( - self.url, - {"type": self.object_type.url}, - **GEO_WRITE_KWARGS, - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f'http://testserver{reverse("object-detail", args=[record.object.uuid])}', - ) diff --git a/src/objects/tests/v1/test_notifications_send.py b/src/objects/tests/v1/test_notifications_send.py deleted file mode 100644 index 43621815..00000000 --- a/src/objects/tests/v1/test_notifications_send.py +++ /dev/null @@ -1,243 +0,0 @@ -from unittest.mock import patch - -from django.test import override_settings - -import requests_mock -from freezegun import freeze_time -from notifications_api_common.models import NotificationsConfig -from rest_framework import status -from rest_framework.test import APITestCase -from zgw_consumers.constants import APITypes -from zgw_consumers.models import Service - -from objects.core.tests.factories import ( - ObjectFactory, - ObjectRecordFactory, - ObjectTypeFactory, -) -from objects.token.constants import PermissionModes -from objects.token.tests.factories import PermissionFactory -from objects.utils.test import TokenAuthMixin - -from ..constants import GEO_WRITE_KWARGS -from ..utils import mock_objecttype, mock_objecttype_version, mock_service_oas_get -from .utils import reverse - -OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" - - -@freeze_time("2018-09-07T00:00:00Z") -@override_settings(NOTIFICATIONS_DISABLED=False) -@requests_mock.Mocker() -class SendNotifTestCase(TokenAuthMixin, APITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - PermissionFactory.create( - object_type=cls.object_type, - mode=PermissionModes.read_and_write, - token_auth=cls.token_auth, - ) - - def setUp(self): - super().setUp() - - service, _ = Service.objects.update_or_create( - api_root="https://notificaties-api.vng.cloud/api/v1/", - defaults=dict( - api_type=APITypes.nrc, - client_id="test", - secret="test", - user_id="test", - user_representation="Test", - ), - ) - config = NotificationsConfig.get_solo() - config.notifications_api_service = service - config.save() - - @patch("notifications_api_common.viewsets.send_notification.delay") - def test_send_notif_create_object(self, mocker, mock_task): - """ - Check if notifications will be send when Object is created - """ - mock_service_oas_get(mocker, OBJECT_TYPES_API, "objecttypes") - mocker.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - mocker.get(self.object_type.url, json=mock_objecttype(self.object_type.url)) - - url = reverse("object-list") - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "geometry": { - "type": "Point", - "coordinates": [4.910649523925713, 52.37240093589432], - }, - "startAt": "2020-01-01", - }, - } - - with self.captureOnCommitCallbacks(execute=True): - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.data) - - data = response.json() - - mock_task.assert_called_once_with( - { - "kanaal": "objecten", - "hoofdObject": data["url"], - "resource": "object", - "resourceUrl": data["url"], - "actie": "create", - "aanmaakdatum": "2018-09-07T02:00:00+02:00", - "kenmerken": { - "objectType": self.object_type.url, - }, - }, - ) - - @patch("notifications_api_common.viewsets.send_notification.delay") - def test_send_notif_update_object(self, mocker, mock_task): - """ - Check if notifications will be send when Object is created - """ - mock_service_oas_get(mocker, OBJECT_TYPES_API, "objecttypes") - mocker.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - mocker.get(self.object_type.url, json=mock_objecttype(self.object_type.url)) - - obj = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=obj) - url = reverse("object-detail", args=[obj.uuid]) - - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "geometry": { - "type": "Point", - "coordinates": [4.910649523925713, 52.37240093589432], - }, - "startAt": "2020-01-01", - }, - } - - with self.captureOnCommitCallbacks(execute=True): - response = self.client.put(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_200_OK, response.data) - - data = response.json() - - mock_task.assert_called_once_with( - { - "kanaal": "objecten", - "hoofdObject": data["url"], - "resource": "object", - "resourceUrl": data["url"], - "actie": "update", - "aanmaakdatum": "2018-09-07T02:00:00+02:00", - "kenmerken": { - "objectType": self.object_type.url, - }, - }, - ) - - @patch("notifications_api_common.viewsets.send_notification.delay") - def test_send_notif_partial_update_object(self, mocker, mock_task): - """ - Check if notifications will be send when Object is created - """ - mock_service_oas_get(mocker, OBJECT_TYPES_API, "objecttypes") - mocker.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - mocker.get(self.object_type.url, json=mock_objecttype(self.object_type.url)) - - obj = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=obj) - url = reverse("object-detail", args=[obj.uuid]) - - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "geometry": { - "type": "Point", - "coordinates": [4.910649523925713, 52.37240093589432], - }, - "startAt": "2020-01-01", - }, - } - - with self.captureOnCommitCallbacks(execute=True): - response = self.client.patch(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_200_OK, response.data) - - data = response.json() - - mock_task.assert_called_once_with( - { - "kanaal": "objecten", - "hoofdObject": data["url"], - "resource": "object", - "resourceUrl": data["url"], - "actie": "partial_update", - "aanmaakdatum": "2018-09-07T02:00:00+02:00", - "kenmerken": { - "objectType": self.object_type.url, - }, - }, - ) - - @patch("notifications_api_common.viewsets.send_notification.delay") - def test_send_notif_delete_object(self, mocker, mock_task): - """ - Check if notifications will be send when Object is created - """ - mock_service_oas_get(mocker, OBJECT_TYPES_API, "objecttypes") - mocker.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - - obj = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create(object=obj) - url = reverse("object-detail", args=[obj.uuid]) - full_url = f"http://testserver{url}" - - with self.captureOnCommitCallbacks(execute=True): - response = self.client.delete(url, **GEO_WRITE_KWARGS) - - self.assertEqual( - response.status_code, status.HTTP_204_NO_CONTENT, response.data - ) - - mock_task.assert_called_once_with( - { - "kanaal": "objecten", - "hoofdObject": full_url, - "resource": "object", - "resourceUrl": full_url, - "actie": "destroy", - "aanmaakdatum": "2018-09-07T02:00:00+02:00", - "kenmerken": { - "objectType": self.object_type.url, - }, - }, - ) diff --git a/src/objects/tests/v1/test_object_api.py b/src/objects/tests/v1/test_object_api.py deleted file mode 100644 index 52667ad7..00000000 --- a/src/objects/tests/v1/test_object_api.py +++ /dev/null @@ -1,303 +0,0 @@ -import json -from datetime import date, timedelta - -import requests_mock -from freezegun import freeze_time -from rest_framework import status -from rest_framework.test import APITestCase - -from objects.core.models import Object -from objects.core.tests.factories import ( - ObjectFactory, - ObjectRecordFactory, - ObjectTypeFactory, -) -from objects.token.constants import PermissionModes -from objects.token.tests.factories import PermissionFactory -from objects.utils.test import TokenAuthMixin - -from ..constants import GEO_WRITE_KWARGS -from ..utils import mock_objecttype, mock_objecttype_version, mock_service_oas_get -from .utils import reverse - -OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" - - -@freeze_time("2020-08-08") -@requests_mock.Mocker() -class ObjectApiTests(TokenAuthMixin, APITestCase): - maxDiff = None - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - PermissionFactory.create( - object_type=cls.object_type, - mode=PermissionModes.read_and_write, - token_auth=cls.token_auth, - ) - - def test_list_actual_objects(self, m): - object_record1 = ObjectRecordFactory.create( - object__object_type=self.object_type, - start_at=date.today(), - ) - object_record2 = ObjectRecordFactory.create( - object__object_type=self.object_type, - start_at=date.today() - timedelta(days=10), - end_at=date.today() - timedelta(days=1), - ) - url = reverse("object-list") - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual(len(data), 1) - self.assertEqual( - data[0]["url"], - f"http://testserver{reverse('object-detail', args=[object_record1.object.uuid])}", - ) - - def test_retrieve_object(self, m): - object = ObjectFactory.create(object_type=self.object_type) - object_record = ObjectRecordFactory.create( - object=object, - start_at=date.today(), - geometry="POINT (4.910649523925713 52.37240093589432)", - ) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual( - data, - { - "url": f'http://testserver{reverse("object-detail", args=[object.uuid])}', - "uuid": str(object.uuid), - "type": object.object_type.url, - "record": { - "index": object_record.index, - "typeVersion": object_record.version, - "data": object_record.data, - "geometry": json.loads(object_record.geometry.json), - "startAt": object_record.start_at.isoformat(), - "endAt": object_record.end_at, - "registrationAt": object_record.registration_at.isoformat(), - "correctionFor": None, - "correctedBy": None, - }, - }, - ) - - def test_create_object(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - m.get(self.object_type.url, json=mock_objecttype(self.object_type.url)) - - url = reverse("object-list") - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "geometry": { - "type": "Point", - "coordinates": [4.910649523925713, 52.37240093589432], - }, - "startAt": "2020-01-01", - }, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - object = Object.objects.get() - - self.assertEqual(object.object_type, self.object_type) - - record = object.records.get() - - self.assertEqual(record.version, 1) - self.assertEqual(record.data, {"plantDate": "2020-04-12", "diameter": 30}) - self.assertEqual(record.start_at, date(2020, 1, 1)) - self.assertEqual(record.registration_at, date(2020, 8, 8)) - self.assertEqual(record.geometry.coords, (4.910649523925713, 52.37240093589432)) - self.assertIsNone(record.end_at) - - def test_update_object(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - m.get(self.object_type.url, json=mock_objecttype(self.object_type.url)) - - # other object - to check that correction works when there is another record with the same index - ObjectRecordFactory.create(object__object_type=self.object_type) - initial_record = ObjectRecordFactory.create( - object__object_type=self.object_type - ) - object = initial_record.object - - assert initial_record.end_at is None - - url = reverse("object-detail", args=[object.uuid]) - data = { - "type": object.object_type.url, - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "geometry": { - "type": "Point", - "coordinates": [4.910649523925713, 52.37240093589432], - }, - "startAt": "2020-01-01", - "correctionFor": initial_record.index, - }, - } - - response = self.client.put(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - object.refresh_from_db() - initial_record.refresh_from_db() - - self.assertEqual(object.object_type, self.object_type) - self.assertEqual(object.records.count(), 2) - - current_record = object.current_record - - self.assertEqual(current_record.version, 1) - self.assertEqual( - current_record.data, {"plantDate": "2020-04-12", "diameter": 30} - ) - self.assertEqual( - current_record.geometry.coords, (4.910649523925713, 52.37240093589432) - ) - self.assertEqual(current_record.start_at, date(2020, 1, 1)) - self.assertEqual(current_record.registration_at, date(2020, 8, 8)) - self.assertIsNone(current_record.end_at) - self.assertEqual(current_record.correct, initial_record) - # assert changes to initial record - self.assertNotEqual(current_record, initial_record) - self.assertEqual(initial_record.corrected, current_record) - self.assertEqual(initial_record.end_at, date(2020, 1, 1)) - - def test_patch_object_record(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - - initial_record = ObjectRecordFactory.create( - version=1, - object__object_type=self.object_type, - start_at=date.today(), - data={"name": "Name", "diameter": 20}, - ) - object = initial_record.object - - url = reverse("object-detail", args=[object.uuid]) - data = { - "record": { - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "startAt": "2020-01-01", - "correctionFor": initial_record.index, - }, - } - - response = self.client.patch(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - initial_record.refresh_from_db() - - self.assertEqual(object.records.count(), 2) - - current_record = object.current_record - - self.assertEqual(current_record.version, initial_record.version) - # The actual behavior of the data merging is in test_merge_patch.py: - self.assertEqual( - current_record.data, - {"plantDate": "2020-04-12", "diameter": 30, "name": "Name"}, - ) - self.assertEqual(current_record.start_at, date(2020, 1, 1)) - self.assertEqual(current_record.registration_at, date(2020, 8, 8)) - self.assertIsNone(current_record.end_at) - self.assertEqual(current_record.correct, initial_record) - # assert changes to initial record - self.assertNotEqual(current_record, initial_record) - self.assertEqual(initial_record.corrected, current_record) - self.assertEqual(initial_record.end_at, date(2020, 1, 1)) - - def test_delete_object(self, m): - record = ObjectRecordFactory.create(object__object_type=self.object_type) - object = record.object - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.delete(url) - - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertEqual(Object.objects.count(), 0) - - def test_history_object(self, m): - record1 = ObjectRecordFactory.create( - object__object_type=self.object_type, - start_at=date(2020, 1, 1), - geometry="POINT (4.910649523925713 52.37240093589432)", - ) - object = record1.object - record2 = ObjectRecordFactory.create( - object=object, start_at=date.today(), correct=record1 - ) - url = reverse("object-history", args=[object.uuid]) - - response = self.client.get(url) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual( - data, - [ - { - "index": 1, - "typeVersion": record1.version, - "data": record1.data, - "geometry": json.loads(record1.geometry.json), - "startAt": record1.start_at.isoformat(), - "endAt": record2.start_at.isoformat(), - "registrationAt": record1.registration_at.isoformat(), - "correctionFor": None, - "correctedBy": 2, - }, - { - "index": 2, - "typeVersion": record2.version, - "data": record2.data, - "geometry": json.loads(record2.geometry.json), - "startAt": record2.start_at.isoformat(), - "endAt": None, - "registrationAt": date.today().isoformat(), - "correctionFor": 1, - "correctedBy": None, - }, - ], - ) diff --git a/src/objects/tests/v1/test_object_api_fields.py b/src/objects/tests/v1/test_object_api_fields.py deleted file mode 100644 index 8c6393b1..00000000 --- a/src/objects/tests/v1/test_object_api_fields.py +++ /dev/null @@ -1,115 +0,0 @@ -from datetime import date - -from rest_framework import status -from rest_framework.test import APITestCase - -from objects.core.tests.factories import ( - ObjectFactory, - ObjectRecordFactory, - ObjectTypeFactory, -) -from objects.token.constants import PermissionModes -from objects.token.tests.factories import PermissionFactory -from objects.utils.test import TokenAuthMixin - -from .utils import reverse - -OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" - - -class DynamicFieldsTests(TokenAuthMixin, APITestCase): - maxDiff = None - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - PermissionFactory.create( - object_type=cls.object_type, - mode=PermissionModes.read_and_write, - token_auth=cls.token_auth, - ) - - def test_list_with_selected_fields(self): - object_record1 = ObjectRecordFactory.create( - object__object_type=self.object_type, start_at=date.today() - ) - object_record2 = ObjectRecordFactory.create( - object__object_type=self.object_type, start_at=date.today() - ) - url = reverse("object-list") - - response = self.client.get( - url, {"fields": "url,type,record__index,record__typeVersion"} - ) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - self.assertEqual(len(data), 2) - self.assertEqual( - data, - [ - { - "url": f"http://testserver{reverse('object-detail', args=[object_record2.object.uuid])}", - "type": self.object_type.url, - "record": {"index": 1, "typeVersion": object_record2.version}, - }, - { - "url": f"http://testserver{reverse('object-detail', args=[object_record1.object.uuid])}", - "type": self.object_type.url, - "record": {"index": 1, "typeVersion": object_record1.version}, - }, - ], - ) - - def test_retrieve_with_selected_fields(self): - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create( - object=object, - start_at=date.today(), - geometry="POINT (4.910649523925713 52.37240093589432)", - ) - url = reverse("object-detail", args=[object.uuid]) - - response = self.client.get(url, {"fields": "url,type,record__geometry"}) - - self.assertEqual(response.status_code, status.HTTP_200_OK) - - data = response.json() - - self.assertEqual( - data, - { - "url": f'http://testserver{reverse("object-detail", args=[object.uuid])}', - "type": object.object_type.url, - "record": { - "geometry": { - "type": "Point", - "coordinates": [4.910649523925713, 52.37240093589432], - }, - }, - }, - ) - - def test_fields_invalid(self): - object = ObjectFactory.create(object_type=self.object_type) - ObjectRecordFactory.create( - object=object, - start_at=date.today(), - geometry="POINT (4.910649523925713 52.37240093589432)", - ) - url = reverse("object-list") - - response = self.client.get(url, {"fields": "url,someField"}) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - data = response.json() - self.assertEqual( - data, - [ - "'fields' query parameter has invalid or unauthorized values: 'someField'" - ], - ) diff --git a/src/objects/tests/v1/test_schema.py b/src/objects/tests/v1/test_schema.py deleted file mode 100644 index e0b07335..00000000 --- a/src/objects/tests/v1/test_schema.py +++ /dev/null @@ -1,10 +0,0 @@ -from rest_framework import status -from rest_framework.test import APITestCase - -from .utils import reverse - - -class APISchemaTest(APITestCase): - def test_schema_endoint(self): - response = self.client.get(reverse("schema-redoc")) - self.assertEqual(response.status_code, status.HTTP_200_OK) diff --git a/src/objects/tests/v1/test_stuf.py b/src/objects/tests/v1/test_stuf.py deleted file mode 100644 index f601f4fe..00000000 --- a/src/objects/tests/v1/test_stuf.py +++ /dev/null @@ -1,556 +0,0 @@ -""" -This test suite asserts that the Objects API implementation supports -material and formal history defined in the StUF 03.01 (Standaard Uitwisseling Formaat) -https://www.gemmaonline.nl/images/gemmaonline/f/fa/Stuf0301.pdf -""" - -from datetime import date - -from rest_framework import status -from rest_framework.test import APITestCase - -from objects.core.tests.factories import ( - ObjectFactory, - ObjectRecordFactory, - ObjectTypeFactory, -) -from objects.token.constants import PermissionModes -from objects.token.tests.factories import PermissionFactory -from objects.utils.test import TokenAuthMixin - -from .utils import reverse - -OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" - - -class Stuf21Tests(TokenAuthMixin, APITestCase): - """ - Test cases based on the Table 2.1 in the StUF 03.01 - |PersoonsId|volgnummer|geslachtsnaam|voorvoegsel|voorletters|geboortedatum|burgerlijkestaat|beginGeldigheid| - |----------|----------|-------------|-----------|-----------|-------------|----------------|---------------| - |5692 |1 |Poepenstaart | |JP |19770807 |ongehuwd |19770807 | - |5692 |40 |Bergh |van den |JP |19770807 |ongehuwd |20010903 | - |5692 |50 |Bergh |van den |JP |19770807 |gehuwd |20050423 | - """ - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - cls.object = ObjectFactory.create(object_type=cls.object_type) - PermissionFactory.create( - object_type=cls.object_type, - mode=PermissionModes.read_and_write, - token_auth=cls.token_auth, - ) - - def setUp(self): - super().setUp() - - self.url = reverse("object-detail", args=[self.object.uuid]) - - def test_1a_1_record_found(self): - """ - Test 1a: If only record 1 (record with volgnummer=1) exists, material history - and formal history on 01-01-2020 should say: Record 1 - """ - record_1 = ObjectRecordFactory.create( - object=self.object, - data={ - "geslachtsnaam": "Poepenstaart", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(1977, 8, 7), - registration_at=date(1977, 8, 7), - ) - - material_response = self.client.get(self.url, {"date": "2020-01-01"}) - formal_response = self.client.get(self.url, {"registrationDate": "2020-01-01"}) - - for response in [formal_response, material_response]: - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json()["url"], f"http://testserver{self.url}") - self.assertEqual(response.json()["record"]["index"], record_1.index) - - def test_1b_1_record_not_found(self): - """ - Test 1b: If only record 1 exists, material history and formal history - on 01-01-1975 should say: No record. - """ - ObjectRecordFactory.create( - object=self.object, - data={ - "geslachtsnaam": "Poepenstaart", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(1977, 8, 7), - registration_at=date(1977, 8, 7), - ) - - material_response = self.client.get(self.url, {"date": "1975-01-01"}) - formal_response = self.client.get(self.url, {"registrationDate": "1975-01-01"}) - - for response in [formal_response, material_response]: - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_2a_2_records_found(self): - """ - Test 2a: If records 1 and 40 exists, material history and formal history on - 01-01-2020 should say: Record 40 - """ - record_1 = ObjectRecordFactory.create( - object=self.object, - data={ - "geslachtsnaam": "Poepenstaart", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(1977, 8, 7), - end_at=date(2001, 9, 3), - registration_at=date(1977, 8, 7), - ) - record_40 = ObjectRecordFactory.create( - object=self.object, - data={ - "geslachtsnaam": "Bergh", - "voorvoegsel": "van den", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(2001, 9, 3), - registration_at=date(2001, 9, 3), - ) - - material_response = self.client.get(self.url, {"date": "2020-01-01"}) - formal_response = self.client.get(self.url, {"registrationDate": "2020-01-01"}) - - for response in [formal_response, material_response]: - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json()["url"], f"http://testserver{self.url}") - self.assertEqual(response.json()["record"]["index"], record_40.index) - - def test_2b_2_records_not_found(self): - """ - Test 2b: If records 1 and 40 exists, material history and formal history - on 01-01-1975 should say: No record. - """ - ObjectRecordFactory.create( - object=self.object, - data={ - "geslachtsnaam": "Poepenstaart", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(1977, 8, 7), - end_at=date(2001, 9, 3), - registration_at=date(1977, 8, 7), - ) - ObjectRecordFactory.create( - object=self.object, - data={ - "geslachtsnaam": "Bergh", - "voorvoegsel": "van den", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(2001, 9, 3), - registration_at=date(2001, 9, 3), - ) - - material_response = self.client.get(self.url, {"date": "1975-01-01"}) - formal_response = self.client.get(self.url, {"registrationDate": "1975-01-01"}) - - for response in [formal_response, material_response]: - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - def test_3a_3_records_found(self): - """ - Test 3a: If records 1, 40 and 50 exists, material history and formal history - on 01-01-2020 should say: Record 50 - """ - record_1 = ObjectRecordFactory.create( - object=self.object, - data={ - "geslachtsnaam": "Poepenstaart", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(1977, 8, 7), - end_at=date(2001, 9, 3), - registration_at=date(1977, 8, 7), - ) - record_40 = ObjectRecordFactory.create( - object=self.object, - data={ - "geslachtsnaam": "Bergh", - "voorvoegsel": "van den", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(2001, 9, 3), - end_at=date(2005, 4, 23), - registration_at=date(2001, 9, 3), - ) - record_50 = ObjectRecordFactory.create( - object=self.object, - data={ - "geslachtsnaam": "Bergh", - "voorvoegsel": "van den", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "gehuwd", - }, - start_at=date(2005, 4, 23), - registration_at=date(2005, 4, 23), - ) - - material_response = self.client.get(self.url, {"date": "2020-01-01"}) - formal_response = self.client.get(self.url, {"registrationDate": "2020-01-01"}) - - for response in [formal_response, material_response]: - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json()["url"], f"http://testserver{self.url}") - self.assertEqual(response.json()["record"]["index"], record_50.index) - - def test_3b_3_records_not_found(self): - """ - Test 3b: If records 1, 40 and 50 exists, material history and formal history - on 01-01-1975 should say: No record. - """ - ObjectRecordFactory.create( - object=self.object, - data={ - "geslachtsnaam": "Poepenstaart", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(1977, 8, 7), - end_at=date(2001, 9, 3), - registration_at=date(1977, 8, 7), - ) - ObjectRecordFactory.create( - object=self.object, - data={ - "geslachtsnaam": "Bergh", - "voorvoegsel": "van den", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(2001, 9, 3), - end_at=date(2005, 4, 23), - registration_at=date(2001, 9, 3), - ) - ObjectRecordFactory.create( - object=self.object, - data={ - "geslachtsnaam": "Bergh", - "voorvoegsel": "van den", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "gehuwd", - }, - start_at=date(2005, 4, 23), - registration_at=date(2005, 4, 23), - ) - - material_response = self.client.get(self.url, {"date": "1975-01-01"}) - formal_response = self.client.get(self.url, {"registrationDate": "1975-01-01"}) - - for response in [formal_response, material_response]: - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - -class Stuf22Tests(TokenAuthMixin, APITestCase): - """ - Test cases based on the Table 2.2 in the StUF 03.01 - |PersoonsId|volgnummer|geslachtsnaam|voorvoegsel|voorletters|geboortedatum|burgerlijkestaat|beginGeldigheid|tijdstipRegistratie| - |----------|----------|-------------|-----------|-----------|-------------|----------------|---------------|-------------------| - |5692 |1 |Poepenstaart | |JP |19770807 |ongehuwd |19770807 |19770815 | - |5692 |10 |Berg |van den |JP |19770807 |ongehuwd |20010903 |20010910 | - |5692 |40 |Bergh |van den |JP |19770807 |ongehuwd |20010903 |20011102 | - |5692 |50 |Bergh |van den |JP |19770807 |gehuwd |20050423 |20050425 | - """ - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - cls.object = ObjectFactory.create(object_type=cls.object_type) - cls.record_1 = ObjectRecordFactory.create( - object=cls.object, - data={ - "geslachtsnaam": "Poepenstaart", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(1977, 8, 7), - end_at=date(2001, 9, 3), - registration_at=date(1977, 8, 15), - ) - cls.record_10 = ObjectRecordFactory.create( - object=cls.object, - data={ - "geslachtsnaam": "Berg", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(2001, 9, 3), - end_at=date(2001, 9, 3), - registration_at=date(2001, 9, 10), - ) - cls.record_40 = ObjectRecordFactory.create( - object=cls.object, - data={ - "geslachtsnaam": "Bergh", - "voorvoegsel": "van den", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(2001, 9, 3), - end_at=date(2005, 4, 23), - registration_at=date(2001, 11, 2), - ) - cls.record_50 = ObjectRecordFactory.create( - object=cls.object, - data={ - "geslachtsnaam": "Bergh", - "voorvoegsel": "van den", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "gehuwd", - }, - start_at=date(2005, 4, 23), - registration_at=date(2005, 4, 25), - ) - - PermissionFactory.create( - object_type=cls.object_type, - mode=PermissionModes.read_and_write, - token_auth=cls.token_auth, - ) - - def setUp(self): - super().setUp() - - self.url = reverse("object-detail", args=[self.object.uuid]) - - def test_4a_present(self): - """ - Test 4a: material history and formal history on 01-01-2020 should say: - Record 50 - """ - material_response = self.client.get(self.url, {"date": "2020-01-01"}) - formal_response = self.client.get(self.url, {"registrationDate": "2020-01-01"}) - - for response in [formal_response, material_response]: - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json()["url"], f"http://testserver{self.url}") - self.assertEqual(response.json()["record"]["index"], self.record_50.index) - - def test_4b_material_history(self): - """ - Test 4b: material history on 01-10-2001 should say: Record 40 - """ - material_response = self.client.get(self.url, {"date": "2001-10-01"}) - - self.assertEqual(material_response.status_code, status.HTTP_200_OK) - self.assertEqual( - material_response.json()["url"], f"http://testserver{self.url}" - ) - self.assertEqual( - material_response.json()["record"]["index"], self.record_40.index - ) - - def test_4c_formal_history(self): - """ - Test 4c: formal history on 01-10-2001 should say: Record 10 - """ - format_response = self.client.get(self.url, {"registrationDate": "2001-10-01"}) - - self.assertEqual(format_response.status_code, status.HTTP_200_OK) - self.assertEqual(format_response.json()["url"], f"http://testserver{self.url}") - self.assertEqual( - format_response.json()["record"]["index"], self.record_10.index - ) - - def test_4d_not_found(self): - """ - Test 4d: material history and formal history on 01-01-1975 should say: - No record. - """ - material_response = self.client.get(self.url, {"date": "1975-01-01"}) - formal_response = self.client.get(self.url, {"registrationDate": "1975-01-01"}) - - for response in [formal_response, material_response]: - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - - -class Stuf23Tests(TokenAuthMixin, APITestCase): - """ - Test cases based on the Table 2.2 in the StUF 03.01 - |PersoonsId|volgnummer|geslachtsnaam|voorvoegsel|voorletters|geboortedatum|burgerlijkestaat|beginGeldigheid|tijdstipRegistratie|volgnrNaCorrectie| - |----------|----------|-------------|-----------|-----------|-------------|----------------|---------------|-------------------|-----------------| - |5692 |1 |Poepenstaart | |JP |19770807 |ongehuwd |19770807 |19770815 | | - |5692 |10 |Berg |van den |JP |19770807 |ongehuwd |20010903 |20010910 |40 | - |5692 |40 |Bergh |van den |JP |19770807 |ongehuwd |20010903 |20011102 | | - |5692 |50 |Bergh |van den |JP |19770807 |gehuwd |20050423 |20050425 | | - """ - - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - cls.object = ObjectFactory.create(object_type=cls.object_type) - cls.record_1 = ObjectRecordFactory.create( - object=cls.object, - data={ - "geslachtsnaam": "Poepenstaart", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(1977, 8, 7), - end_at=date(2001, 9, 3), - registration_at=date(1977, 8, 15), - ) - cls.record_10 = ObjectRecordFactory.create( - object=cls.object, - data={ - "geslachtsnaam": "Berg", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(2001, 9, 3), - end_at=date(2001, 9, 3), - registration_at=date(2001, 9, 10), - ) - cls.record_40 = ObjectRecordFactory.create( - object=cls.object, - data={ - "geslachtsnaam": "Bergh", - "voorvoegsel": "van den", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "ongehuwd", - }, - start_at=date(2001, 9, 3), - end_at=date(2005, 4, 23), - registration_at=date(2001, 11, 2), - correct=cls.record_10, - ) - cls.record_50 = ObjectRecordFactory.create( - object=cls.object, - data={ - "geslachtsnaam": "Bergh", - "voorvoegsel": "van den", - "voorletters": "JP", - "geboortedatum": "1977-08-07", - "burgerlijkestaat": "gehuwd", - }, - start_at=date(2005, 4, 23), - registration_at=date(2005, 4, 25), - ) - - PermissionFactory.create( - object_type=cls.object_type, - mode=PermissionModes.read_and_write, - token_auth=cls.token_auth, - ) - - def setUp(self): - super().setUp() - - self.url = reverse("object-detail", args=[self.object.uuid]) - - def test_5a_present(self): - """ - Test 5a: material history and formal history on 01-01-2020 should say: - Record 50 - """ - material_response = self.client.get(self.url, {"date": "2020-01-01"}) - formal_response = self.client.get(self.url, {"registrationDate": "2020-01-01"}) - - for response in [formal_response, material_response]: - self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.json()["url"], f"http://testserver{self.url}") - self.assertEqual(response.json()["record"]["index"], self.record_50.index) - - def test_5b_material_history(self): - """ - Test 5b: material history on 01-10-2001 should say: Records 40. - """ - material_response = self.client.get(self.url, {"date": "2001-10-01"}) - - self.assertEqual(material_response.status_code, status.HTTP_200_OK) - self.assertEqual( - material_response.json()["url"], f"http://testserver{self.url}" - ) - self.assertEqual( - material_response.json()["record"]["index"], self.record_40.index - ) - - def test_5c_material_history(self): - """ - Test 5c: material history on 04-09-2001 should say: Record 10. - """ - material_response = self.client.get(self.url, {"date": "2001-09-04"}) - - self.assertEqual(material_response.status_code, status.HTTP_200_OK) - self.assertEqual( - material_response.json()["url"], f"http://testserver{self.url}" - ) - self.assertEqual( - material_response.json()["record"]["index"], self.record_40.index - ) - - def test_5d_formal_history(self): - """ - Test 5d: formal history on 01-10-2001 should say: Record 10 - """ - format_response = self.client.get(self.url, {"registrationDate": "2001-10-01"}) - - self.assertEqual(format_response.status_code, status.HTTP_200_OK) - self.assertEqual(format_response.json()["url"], f"http://testserver{self.url}") - self.assertEqual( - format_response.json()["record"]["index"], self.record_10.index - ) - - def test_5e_formal_history(self): - """ - est 5e: formal history on 04-09-2001 should say: Record 40 - """ - format_response = self.client.get(self.url, {"registrationDate": "2001-09-04"}) - - self.assertEqual(format_response.status_code, status.HTTP_200_OK) - self.assertEqual(format_response.json()["url"], f"http://testserver{self.url}") - self.assertEqual(format_response.json()["record"]["index"], self.record_1.index) - - def test_5f_not_found(self): - """ - Test 4d: material history and formal history on 01-01-1975 should say: - No record. - """ - material_response = self.client.get(self.url, {"date": "1975-01-01"}) - formal_response = self.client.get(self.url, {"registrationDate": "1975-01-01"}) - - for response in [formal_response, material_response]: - self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) diff --git a/src/objects/tests/v1/test_validation.py b/src/objects/tests/v1/test_validation.py deleted file mode 100644 index 96b85420..00000000 --- a/src/objects/tests/v1/test_validation.py +++ /dev/null @@ -1,413 +0,0 @@ -import uuid - -import requests -import requests_mock -from rest_framework import status -from rest_framework.test import APITestCase - -from objects.core.models import Object -from objects.core.tests.factories import ObjectRecordFactory, ObjectTypeFactory -from objects.token.constants import PermissionModes -from objects.token.tests.factories import PermissionFactory -from objects.utils.test import TokenAuthMixin - -from ..constants import GEO_WRITE_KWARGS -from ..utils import mock_objecttype, mock_objecttype_version, mock_service_oas_get -from .utils import reverse - -OBJECT_TYPES_API = "https://example.com/objecttypes/v1/" - - -@requests_mock.Mocker() -class ObjectTypeValidationTests(TokenAuthMixin, APITestCase): - @classmethod - def setUpTestData(cls): - super().setUpTestData() - - cls.object_type = ObjectTypeFactory(service__api_root=OBJECT_TYPES_API) - PermissionFactory.create( - object_type=cls.object_type, - mode=PermissionModes.read_and_write, - token_auth=cls.token_auth, - ) - - def test_create_object_with_not_found_objecttype_url(self, m): - object_type_invalid = ObjectTypeFactory(service=self.object_type.service) - PermissionFactory.create( - object_type=object_type_invalid, - mode=PermissionModes.read_and_write, - token_auth=self.token_auth, - ) - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get(f"{object_type_invalid.url}/versions/1", status_code=404) - - url = reverse("object-list") - data = { - "type": object_type_invalid.url, - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12"}, - "startAt": "2020-01-01", - }, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(Object.objects.count(), 0) - - def test_create_object_with_invalid_length(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - object_type_long = f"{OBJECT_TYPES_API}{'a'*1000}/{self.object_type.uuid}" - data = { - "type": object_type_long, - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "startAt": "2020-01-01", - }, - } - url = reverse("object-list") - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(Object.objects.count(), 0) - - data = response.json() - self.assertEqual(data["type"], ["The value has too many characters"]) - - def test_create_object_no_version(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get(f"{self.object_type.url}/versions/10", status_code=404) - - url = reverse("object-list") - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 10, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "startAt": "2020-01-01", - }, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(Object.objects.count(), 0) - - data = response.json() - self.assertEqual( - data["non_field_errors"], ["Object type doesn't have retrievable data."] - ) - - def test_create_object_objecttype_request_error(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get(f"{self.object_type.url}/versions/10", exc=requests.HTTPError) - - url = reverse("object-list") - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 10, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "startAt": "2020-01-01", - }, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(Object.objects.count(), 0) - - data = response.json() - self.assertEqual( - data["non_field_errors"], ["Object type version can not be retrieved."] - ) - - def test_create_object_objecttype_with_no_jsonSchema(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get( - f"{self.object_type.url}/versions/10", - status_code=200, - json={"key": "value"}, - ) - - url = reverse("object-list") - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 10, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "startAt": "2020-01-01", - }, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(Object.objects.count(), 0) - - data = response.json() - self.assertEqual( - data["non_field_errors"], - [ - f"{self.object_type.url}/versions/10 does not appear to be a valid objecttype." - ], - ) - - def test_create_object_schema_invalid(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - - url = reverse("object-list") - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12"}, - "startAt": "2020-01-01", - }, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(Object.objects.count(), 0) - - data = response.json() - self.assertEqual( - data["non_field_errors"], ["'diameter' is a required property"] - ) - - def test_create_object_without_record_invalid(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - - url = reverse("object-list") - data = { - "type": self.object_type.url, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(Object.objects.count(), 0) - - def test_create_object_correction_invalid(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - - record = ObjectRecordFactory.create() - url = reverse("object-list") - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "startAt": "2020-01-01", - "correctionFor": record.index, - }, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(Object.objects.exclude(id=record.object.id).count(), 0) - - data = response.json() - self.assertEqual( - data["record"]["correctionFor"], - [f"Object with index={record.index} does not exist."], - ) - - def test_create_object_geometry_not_allowed(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - m.get( - self.object_type.url, - json=mock_objecttype(self.object_type.url, attrs={"allowGeometry": False}), - ) - - url = reverse("object-list") - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "geometry": { - "type": "Point", - "coordinates": [4.910649523925713, 52.37240093589432], - }, - "startAt": "2020-01-01", - }, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json()["non_field_errors"], - ["This object type doesn't support geometry"], - ) - - def test_create_object_with_geometry_without_allowGeometry(self, m): - """test the support of Objecttypes api without allowGeometry property""" - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - object_type_response = mock_objecttype(self.object_type.url) - del object_type_response["allowGeometry"] - m.get(self.object_type.url, json=object_type_response) - m.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - - url = reverse("object-list") - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "geometry": { - "type": "Point", - "coordinates": [4.910649523925713, 52.37240093589432], - }, - "startAt": "2020-01-01", - }, - } - - response = self.client.post(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - def test_update_object_with_correction_invalid(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - - corrected_record, initial_record = ObjectRecordFactory.create_batch( - 2, object__object_type=self.object_type - ) - object = initial_record.object - url = reverse("object-detail", args=[object.uuid]) - data = { - "type": self.object_type.url, - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "startAt": "2020-01-01", - "correctionFor": 5, - }, - } - - response = self.client.put(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - data = response.json() - self.assertEqual( - data["record"]["correctionFor"], - ["Object with index=5 does not exist."], - ) - - def test_update_object_type_invalid(self, m): - old_object_type = ObjectTypeFactory(service=self.object_type.service) - PermissionFactory.create( - object_type=old_object_type, - mode=PermissionModes.read_and_write, - token_auth=self.token_auth, - ) - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get(self.object_type.url, json=mock_objecttype(self.object_type.url)) - - initial_record = ObjectRecordFactory.create( - object__object_type=old_object_type, - data={"plantDate": "2020-04-12", "diameter": 30}, - version=1, - ) - object = initial_record.object - - url = reverse("object-detail", args=[object.uuid]) - data = { - "type": self.object_type.url, - } - - response = self.client.patch(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - data = response.json() - self.assertEqual( - data["type"], - ["This field can't be changed"], - ) - - def test_update_uuid_invalid(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get(self.object_type.url, json=mock_objecttype(self.object_type.url)) - - initial_record = ObjectRecordFactory.create( - object__object_type=self.object_type - ) - object = initial_record.object - - url = reverse("object-detail", args=[object.uuid]) - data = {"uuid": uuid.uuid4()} - - response = self.client.patch(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - - data = response.json() - self.assertEqual(data["uuid"], ["This field can't be changed"]) - - def test_update_geometry_not_allowed(self, m): - mock_service_oas_get(m, OBJECT_TYPES_API, "objecttypes") - m.get( - f"{self.object_type.url}/versions/1", - json=mock_objecttype_version(self.object_type.url), - ) - m.get( - self.object_type.url, - json=mock_objecttype(self.object_type.url, attrs={"allowGeometry": False}), - ) - - initial_record = ObjectRecordFactory.create( - object__object_type=self.object_type, geometry=None - ) - object = initial_record.object - - url = reverse("object-detail", args=[object.uuid]) - data = { - "record": { - "typeVersion": 1, - "data": {"plantDate": "2020-04-12", "diameter": 30}, - "geometry": { - "type": "Point", - "coordinates": [4.910649523925713, 52.37240093589432], - }, - "startAt": "2020-01-01", - } - } - - response = self.client.patch(url, data, **GEO_WRITE_KWARGS) - - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json()["non_field_errors"], - ["This object type doesn't support geometry"], - ) diff --git a/src/objects/tests/v1/utils.py b/src/objects/tests/v1/utils.py deleted file mode 100644 index b78aa92a..00000000 --- a/src/objects/tests/v1/utils.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.urls import reverse as _reverse -from django.utils.functional import lazy - -VERSION = "v1" - - -def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None): - """always return api endpoints with predefined version""" - viewname = f"{VERSION}:{viewname}" - return _reverse(viewname, urlconf, args, kwargs, current_app) - - -reverse_lazy = lazy(reverse, str) diff --git a/src/objects/utils/hooks.py b/src/objects/utils/hooks.py deleted file mode 100644 index 07376d66..00000000 --- a/src/objects/utils/hooks.py +++ /dev/null @@ -1,13 +0,0 @@ -from django.conf import settings - - -def postprocess_versions(result, generator, **kwargs): - major_version = result["info"]["version"] - result["info"]["version"] = f"{settings.VERSIONS[major_version]} ({major_version})" - return result - - -def postprocess_servers(result, generator, **kwargs): - major_version = result["info"]["version"] - result["servers"] = settings.OAS_SERVERS[major_version] - return result