Ensure instance-state updates when collections are PUT or DELETE-ed #252
3 changed files with 108 additions and 13 deletions
26
CHANGELOG.md
26
CHANGELOG.md
|
|
@ -1,3 +1,29 @@
|
|||
# 6.3.1 (2026-07-03)
|
||||
|
||||
## Changes
|
||||
|
||||
- Use canonical storage representation of records. This reduces "noise" in
|
||||
audit-backends by minimizing the differences between record versions. This
|
||||
also reduces the noise in change set in the `dtc`-subcommand `auto-curate`.
|
||||
|
||||
- `GET /tokens`-endpoint now returns full token information, including the
|
||||
token representation. This allows to fully reproduce a configuration on
|
||||
a different server via API-calls only.
|
||||
|
||||
- `gitaudit`-audit backends now use a fixed user-id. This prevents start up
|
||||
errors in environments where no git-user id is configured.
|
||||
|
||||
|
||||
# 6.3.0 (2026-06-29)
|
||||
|
||||
## Changes
|
||||
|
||||
- Automated annotation adding in write-record-endpoints has been removed.
|
||||
The new query parameter `add_submission_tag` is added to write-record-endpoints.
|
||||
If set to True, the server will add an automated annotation with the annotation
|
||||
tags that are defined in the server-configuration (the default is False).
|
||||
|
||||
|
||||
# 6.3.0 (2026-06-29)
|
||||
|
||||
## Changes
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import logging
|
||||
from typing import cast
|
||||
|
||||
from fastapi_pagination import add_pagination
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ tag_groups = [
|
|||
'incoming_write',
|
||||
]
|
||||
|
||||
openapi_tags_template = [
|
||||
openapi_tags_template: list[dict | str] = [
|
||||
{
|
||||
'name': 'Server management',
|
||||
'description': 'General server operations',
|
||||
|
|
@ -115,7 +116,7 @@ def manifest_configuration(
|
|||
# because token-objects are all re-created below).
|
||||
for collection_name in deleted_collection_names:
|
||||
delete_endpoints_for_collection(instance_state, collection_name)
|
||||
delete_collection(instance_state, collection_name)
|
||||
delete_collection_from_instance_state(instance_state, collection_name)
|
||||
|
||||
# Create the internal representation objects for collections that have been
|
||||
# added to the configuration.
|
||||
|
|
@ -172,21 +173,28 @@ def delete_token(
|
|||
instance_state.tokens.pop(token_name)
|
||||
|
||||
|
||||
def delete_collection(
|
||||
def delete_collection_from_instance_state(
|
||||
instance_state: InstanceState,
|
||||
collection_name: str,
|
||||
):
|
||||
instance_state.collections.pop(collection_name)
|
||||
|
||||
# TODO: remove further collection-related information from
|
||||
# instance_state. Maybe all collection-specific information
|
||||
# TODO: Maybe all collection-specific information
|
||||
# should go into the instance_state.collection[x]-object!?
|
||||
# That would allow to remove it easily.
|
||||
|
||||
# Remove objects that exist for every collection
|
||||
instance_state.collections.pop(collection_name)
|
||||
instance_state.curated_stores.pop(collection_name)
|
||||
instance_state.validators.pop(collection_name)
|
||||
instance_state.auth_sources.pop(collection_name)
|
||||
|
||||
# Remove optional objects
|
||||
instance_state.audit_backends.pop(collection_name, None)
|
||||
instance_state.incoming_stores.pop(collection_name, None)
|
||||
|
||||
|
||||
def create_openapi_tags(
|
||||
instance_state: InstanceState,
|
||||
openapi_tags_template: list[dict | str],
|
||||
openapi_tags_template_: list[dict | str],
|
||||
) -> list[dict]:
|
||||
# Collect tag name lists for all tag groups that we have defined.
|
||||
tag_group_info = {
|
||||
|
|
@ -199,8 +207,8 @@ def create_openapi_tags(
|
|||
)
|
||||
for tag_group in tag_groups
|
||||
}
|
||||
result = openapi_tags_template.copy()
|
||||
result = openapi_tags_template_.copy()
|
||||
for tag_group, tag_list in tag_group_info.items():
|
||||
index = result.index(tag_group)
|
||||
result[index : index + 1] = tag_list
|
||||
return result
|
||||
return cast('list[dict]', result)
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ from dump_things_service import (
|
|||
HTTP_404_NOT_FOUND,
|
||||
)
|
||||
from dump_things_service.abstract_config import (
|
||||
ForgejoAuthSpec,
|
||||
GitAuditBackendConfig,
|
||||
TokenCollectionConfig,
|
||||
TokenModes,
|
||||
hash_token_representation,
|
||||
)
|
||||
from dump_things_service.collection_endpoints import CollectionRequest
|
||||
from dump_things_service.instance_state import get_instance_state
|
||||
from dump_things_service.token_endpoints import (
|
||||
AdminTokenRequest,
|
||||
TokenRequest,
|
||||
|
|
@ -82,15 +84,18 @@ def test_collection_adding(fastapi_client_simple):
|
|||
assert response.status_code == HTTP_404_NOT_FOUND
|
||||
assert not _name_in_openapi_paths(test_client, new_collection_name)
|
||||
|
||||
# Add a new collection
|
||||
# Add a new collection to the configuration
|
||||
response = test_client.post(
|
||||
'/collections',
|
||||
headers={'x-dumpthings-token': admin_token},
|
||||
json=new_collection_request.model_dump(mode='json', by_alias=True),
|
||||
)
|
||||
assert response.status_code == HTTP_201_CREATED
|
||||
|
||||
# Check that the collection endpoints exist in the open API document
|
||||
assert _name_in_openapi_paths(test_client, new_collection_name)
|
||||
|
||||
# Read back the new collection and compare it
|
||||
response = test_client.get(
|
||||
f'/collections/{new_collection_name}',
|
||||
headers={'x-dumpthings-token': admin_token},
|
||||
|
|
@ -124,13 +129,12 @@ def test_collection_adding(fastapi_client_simple):
|
|||
'representation': new_token_request.representation,
|
||||
}
|
||||
|
||||
# Add a new record to the collection to verify that the token works
|
||||
new_record = {
|
||||
'pid': 'http://example.com/admin-test-1',
|
||||
'given_name': 'Admin Test 1',
|
||||
'schema_type': 'abc:Person',
|
||||
}
|
||||
|
||||
# Add a record to the collection
|
||||
response = test_client.post(
|
||||
f'/{new_collection_name}/record/Person',
|
||||
headers={'x-dumpthings-token': new_token_representation},
|
||||
|
|
@ -313,3 +317,60 @@ def test_admin_token_management(fastapi_client_simple):
|
|||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
assert new_admin_token_name not in response.json()
|
||||
|
||||
|
||||
def test_instance_state_updates(fastapi_client_simple):
|
||||
test_client, _, admin_token = fastapi_client_simple
|
||||
|
||||
instance_state = get_instance_state()
|
||||
|
||||
forgejo_auth_spec = ForgejoAuthSpec(
|
||||
type='forgejo',
|
||||
url='http://localhost',
|
||||
organization='orga-1',
|
||||
team='team-1',
|
||||
label_type='user',
|
||||
)
|
||||
|
||||
forgejo_audit_collection_name = 'test_forgejo_audit_collection'
|
||||
forgejo_audit_collection_request = CollectionRequest(
|
||||
name=forgejo_audit_collection_name,
|
||||
default_token='test_default_token',
|
||||
curated=PurePosixPath(f'{curated}/{forgejo_audit_collection_name}'),
|
||||
schema=test_schema_location,
|
||||
incoming=PurePosixPath(f'{incoming}/{forgejo_audit_collection_name}'),
|
||||
auth_sources=[forgejo_auth_spec],
|
||||
)
|
||||
|
||||
# Post a collection with forgejo audit-backend without an instance_id
|
||||
response = test_client.post(
|
||||
'/collections',
|
||||
headers={'x-dumpthings-token': admin_token},
|
||||
json=forgejo_audit_collection_request.model_dump(mode='json', by_alias=True),
|
||||
)
|
||||
assert response.status_code == HTTP_201_CREATED
|
||||
forgejo_instance = instance_state.auth_sources[forgejo_audit_collection_name][0]
|
||||
assert forgejo_instance.instance_id is None
|
||||
|
||||
# Put a collection with updated forgejo audit-backend, containing an instance_id
|
||||
forgejo_auth_spec.instance_id = 'instance-id'
|
||||
response = test_client.put(
|
||||
'/collections',
|
||||
headers={'x-dumpthings-token': admin_token},
|
||||
json=forgejo_audit_collection_request.model_dump(mode='json', by_alias=True),
|
||||
)
|
||||
assert response.status_code == HTTP_201_CREATED
|
||||
auth_sources = instance_state.auth_sources[forgejo_audit_collection_name]
|
||||
assert len(auth_sources) == 1
|
||||
new_forgejo_instance = auth_sources[0]
|
||||
|
||||
assert new_forgejo_instance is not forgejo_instance
|
||||
assert new_forgejo_instance.instance_id == 'instance-id'
|
||||
|
||||
# Delete the collection again because we check for a known number of
|
||||
# collections in other tests.
|
||||
response = test_client.delete(
|
||||
f'/collections/{forgejo_audit_collection_name}',
|
||||
headers={'x-dumpthings-token': admin_token},
|
||||
)
|
||||
assert response.status_code == HTTP_200_OK
|
||||
|
|
|
|||
Loading…
Reference in a new issue