The test-fixture `fast_api_simple` now returns a tuple containing: - test_client instance - store path - admin token
433 lines
13 KiB
Python
433 lines
13 KiB
Python
import sys
|
|
from pathlib import (
|
|
Path,
|
|
PurePosixPath,
|
|
)
|
|
from types import ModuleType
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
from dump_things_service.abstract_config import (
|
|
GitAuditBackendConfig,
|
|
SQLiteBackendConfig,
|
|
TokenCollectionConfig,
|
|
TokenModes, hash_token_representation,
|
|
)
|
|
from dump_things_service.backends import StorageBackend
|
|
from dump_things_service.backends.record_dir import RecordDirStore
|
|
from dump_things_service.backends.sqlite import (
|
|
SQLiteBackend,
|
|
record_file_name as sqlite_db_filename,
|
|
)
|
|
from dump_things_service.collection_endpoints import CollectionRequest
|
|
from dump_things_service.instance_state import get_mapping_function_by_name
|
|
from dump_things_service.model import get_model_for_schema
|
|
from dump_things_service.resolve_curie import resolve_curie
|
|
from dump_things_service.token_endpoints import TokenRequest
|
|
from dump_things_service.tests.create_store import (
|
|
pid,
|
|
pid_curated,
|
|
pid_trr,
|
|
test_record,
|
|
test_record_curated,
|
|
test_record_trr,
|
|
)
|
|
|
|
|
|
# String representation of curated- and incoming-path
|
|
curated = 'curated'
|
|
incoming = 'incoming'
|
|
|
|
# Path to a local simple test schema
|
|
test_schema_location = str((Path(__file__).parent / 'testschema.yaml').absolute())
|
|
flat_social_schema_location = 'https://concepts.datalad.org/s/flat-social/unreleased.yaml'
|
|
|
|
|
|
# The test store is created empty and collections are added via the admin
|
|
# web interface.
|
|
g_default_collections = [
|
|
CollectionRequest(
|
|
name=f'collection_{i}',
|
|
default_token='test_default_token',
|
|
curated=PurePosixPath(f'{curated}/collection_{i}'),
|
|
schema=test_schema_location,
|
|
incoming=PurePosixPath(f'{incoming}/collection_{i}'),
|
|
)
|
|
for i in range(1, 8)
|
|
]
|
|
|
|
g_default_collections.append(
|
|
CollectionRequest(
|
|
name=f'collection_8',
|
|
default_token='test_default_token',
|
|
curated=PurePosixPath(f'{curated}/collection_8'),
|
|
schema=test_schema_location,
|
|
incoming=PurePosixPath(f'{incoming}/collection_8'),
|
|
backend=SQLiteBackendConfig(
|
|
type='sqlite',
|
|
)
|
|
)
|
|
)
|
|
|
|
g_default_collections.extend([
|
|
CollectionRequest(
|
|
name='collection_dlflatsocial-1',
|
|
schema=flat_social_schema_location,
|
|
default_token='test_default_token',
|
|
curated=PurePosixPath(f'{curated}/collection_dlflatsocial-1'),
|
|
incoming=PurePosixPath(f'{incoming}/collection_dlflatsocial-1'),
|
|
),
|
|
CollectionRequest(
|
|
name='collection_dlflatsocial-2',
|
|
schema=flat_social_schema_location,
|
|
default_token='test_default_token',
|
|
curated=PurePosixPath(f'{curated}/collection_dlflatsocial-2'),
|
|
incoming=PurePosixPath(f'{incoming}/collection_dlflatsocial-2'),
|
|
backend=SQLiteBackendConfig(
|
|
type='sqlite',
|
|
),
|
|
use_classes=[
|
|
'Organization',
|
|
'Person',
|
|
'Project',
|
|
],
|
|
ignore_classes=[
|
|
'Organization',
|
|
'Project',
|
|
],
|
|
),
|
|
])
|
|
|
|
g_default_tokens = [
|
|
TokenRequest(
|
|
name='test_default_token',
|
|
user_id='basic_access_user',
|
|
hashed=False,
|
|
representation='basic_access',
|
|
collections={
|
|
**{
|
|
f'collection_{i}': TokenCollectionConfig(
|
|
mode=TokenModes.READ_CURATED,
|
|
)
|
|
for i in range(1, 9)
|
|
},
|
|
**{
|
|
f'collection_dlflatsocial-{i}': TokenCollectionConfig(
|
|
mode=TokenModes.READ_CURATED,
|
|
)
|
|
for i in range(1, 3)
|
|
},
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test token for some collections',
|
|
user_id='test_user_1',
|
|
hashed=False,
|
|
representation='token-1',
|
|
collections={
|
|
collection_name: TokenCollectionConfig(
|
|
mode=TokenModes.WRITE_COLLECTION,
|
|
incoming_label='in_token_1',
|
|
)
|
|
for collection_name in (
|
|
'collection_1',
|
|
'collection_dlflatsocial-1',
|
|
'collection_dlflatsocial-2',
|
|
)
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test token for collection_2',
|
|
user_id='test_user_2',
|
|
hashed=False,
|
|
representation='token-2',
|
|
collections={
|
|
f'collection_2': TokenCollectionConfig(
|
|
mode=TokenModes.WRITE_COLLECTION,
|
|
incoming_label='in_token-2',
|
|
)
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test token for collection_8',
|
|
user_id='test_user_8',
|
|
hashed=False,
|
|
representation='token-8',
|
|
collections={
|
|
f'collection_8': TokenCollectionConfig(
|
|
mode=TokenModes.WRITE_COLLECTION,
|
|
incoming_label='test_user_8',
|
|
)
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test token for all collections',
|
|
user_id='user_all',
|
|
hashed=False,
|
|
representation='token-all',
|
|
collections={
|
|
**{
|
|
f'collection_{i}': TokenCollectionConfig(
|
|
mode=TokenModes.WRITE_COLLECTION,
|
|
incoming_label='token-all:user_all',
|
|
)
|
|
for i in range(1, 9)
|
|
},
|
|
**{
|
|
f'collection_dlflatsocial-{i}': TokenCollectionConfig(
|
|
mode=TokenModes.WRITE_COLLECTION,
|
|
incoming_label='token-all:user_all',
|
|
)
|
|
for i in range(1, 3)
|
|
},
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test Curator Token',
|
|
user_id='test_curator',
|
|
representation='token_curator',
|
|
collections={
|
|
f'collection_{i}': TokenCollectionConfig(
|
|
mode=TokenModes.CURATOR,
|
|
incoming_label=f'admin_{i}' if i < 5 else 'admin_common',
|
|
)
|
|
for i in range(1, 9)
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test Hashed Token',
|
|
user_id='test_hashed',
|
|
representation='token-hashed',
|
|
collections={
|
|
'collection_1': TokenCollectionConfig(
|
|
mode=TokenModes.WRITE_COLLECTION,
|
|
incoming_label='token-hashed-1',
|
|
),
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test XX000 (READ_COLLECTION)',
|
|
user_id='test_user_1_read_collection',
|
|
representation='token_1_xxooo',
|
|
collections={
|
|
'collection_1': TokenCollectionConfig(
|
|
mode=TokenModes.READ_COLLECTION,
|
|
incoming_label='modes',
|
|
),
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test XXX00 (WRITE_COLLECTION)',
|
|
user_id='test_user_1_write_collection',
|
|
representation='token_1_xxxoo',
|
|
collections={
|
|
'collection_1': TokenCollectionConfig(
|
|
mode=TokenModes.WRITE_COLLECTION,
|
|
incoming_label='modes',
|
|
),
|
|
}
|
|
),
|
|
TokenRequest(
|
|
name='Test 0X000 (READ_SUBMISSIONS)',
|
|
user_id='test_user_1_read_submissions',
|
|
representation='token_1_oxooo',
|
|
collections={
|
|
'collection_1': TokenCollectionConfig(
|
|
mode=TokenModes.READ_SUBMISSIONS,
|
|
incoming_label='modes',
|
|
),
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test 0XX00 (WRITE_SUBMISSIONS)',
|
|
user_id='test_user_1_write_submissions',
|
|
representation='token_1_oxxoo',
|
|
collections={
|
|
'collection_1': TokenCollectionConfig(
|
|
mode=TokenModes.WRITE_SUBMISSIONS,
|
|
incoming_label='modes',
|
|
),
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test X0X00 (SUBMIT)',
|
|
user_id='test_user_1_submit',
|
|
representation='token_1_xoxoo',
|
|
collections={
|
|
'collection_1': TokenCollectionConfig(
|
|
mode=TokenModes.SUBMIT,
|
|
incoming_label='modes',
|
|
),
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test 00X00 (SUBMIT_ONLY)',
|
|
user_id='test_user_1_submit_only',
|
|
representation='token_1_ooxoo',
|
|
collections={
|
|
'collection_1': TokenCollectionConfig(
|
|
mode=TokenModes.SUBMIT_ONLY,
|
|
incoming_label='modes',
|
|
),
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test X0000 (READ_CURATED)',
|
|
user_id='test_user_1_read_curated',
|
|
representation='token_1_xoooo',
|
|
collections={
|
|
'collection_1': TokenCollectionConfig(
|
|
mode=TokenModes.READ_CURATED,
|
|
incoming_label='modes',
|
|
),
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test 00000 (NOTHING)',
|
|
user_id='test_user_1_nothing',
|
|
representation='token_1_ooooo',
|
|
collections={
|
|
'collection_1': TokenCollectionConfig(
|
|
mode=TokenModes.NOTHING,
|
|
incoming_label='modes',
|
|
),
|
|
},
|
|
),
|
|
TokenRequest(
|
|
name='Test XXXXX (CURATOR)',
|
|
user_id='test_user_1_curator',
|
|
representation='token_1_xxxxx',
|
|
collections={
|
|
'collection_1': TokenCollectionConfig(
|
|
mode=TokenModes.CURATOR,
|
|
incoming_label='modes',
|
|
),
|
|
'collection_8': TokenCollectionConfig(
|
|
mode=TokenModes.CURATOR,
|
|
incoming_label='modes',
|
|
),
|
|
},
|
|
),
|
|
]
|
|
|
|
g_default_entries = {
|
|
f'collection_{i}': [('Person', pid, test_record)] for i in range(1, 9)
|
|
}
|
|
for collection_id in range(1, 9):
|
|
g_default_entries[f'collection_{collection_id}'].extend(
|
|
[
|
|
('Person', pid_curated, test_record_curated),
|
|
(
|
|
'Person',
|
|
'abc:mode_test',
|
|
'pid: abc:mode_test\ngiven_name: mode_curated\nschema_type: abc:Person\n',
|
|
),
|
|
]
|
|
)
|
|
|
|
g_default_entries['collection_dlflatsocial-1'] = [('Person', pid_trr, test_record_trr)]
|
|
g_default_entries['collection_dlflatsocial-2'] = [('Person', pid_trr, test_record_trr)]
|
|
|
|
|
|
@pytest.fixture(scope='session')
|
|
def dump_stores_simple(tmp_path_factory):
|
|
tmp_path = tmp_path_factory.mktemp('dump_store')
|
|
audit_store_path = tmp_path_factory.mktemp('audit_store')
|
|
return tmp_path, audit_store_path
|
|
|
|
|
|
@pytest.fixture(scope='session')
|
|
def fastapi_app_simple(dump_stores_simple):
|
|
tmp_path, audit_tmp_path = dump_stores_simple
|
|
|
|
admin_token = 'admin-1'
|
|
old_sys_argv = sys.argv
|
|
sys.argv = [
|
|
'test-runner',
|
|
'--admin-token-hash', hash_token_representation(admin_token),
|
|
str(tmp_path),
|
|
]
|
|
from dump_things_service.main import app
|
|
|
|
sys.argv = old_sys_argv
|
|
return app, tmp_path, audit_tmp_path, admin_token
|
|
|
|
|
|
@pytest.fixture(scope='session')
|
|
def fastapi_client_simple(fastapi_app_simple):
|
|
from fastapi.testclient import TestClient
|
|
|
|
test_client = TestClient(fastapi_app_simple[0])
|
|
store_path = fastapi_app_simple[1]
|
|
audit_path = fastapi_app_simple[2]
|
|
admin_token = fastapi_app_simple[3]
|
|
|
|
# Add an audit backend to the first collection in g_default_collections
|
|
assert g_default_collections[0].name == 'collection_1'
|
|
g_default_collections[0].audit_backends = [
|
|
GitAuditBackendConfig(
|
|
type='gitaudit',
|
|
path=Path(audit_path),
|
|
auto_flush_timeout=2,
|
|
)
|
|
]
|
|
|
|
# Add collections via the Web-API
|
|
for collection_config in g_default_collections:
|
|
response = test_client.post(
|
|
'/collections',
|
|
json=collection_config.model_dump(
|
|
exclude_unset=True,
|
|
mode='json',
|
|
by_alias=True,
|
|
),
|
|
headers={'x-dumpthings-token': admin_token},
|
|
)
|
|
assert response.status_code == 201
|
|
|
|
# Add tokens via Web-API
|
|
for token_config in g_default_tokens:
|
|
response = test_client.post(
|
|
'/tokens',
|
|
json=token_config.model_dump(exclude_unset=True, mode='json'),
|
|
headers={'x-dumpthings-token': admin_token},
|
|
)
|
|
assert response.status_code == 201
|
|
|
|
# Add default content via backend instances
|
|
for collection_config in g_default_collections:
|
|
curated_path = Path(store_path / collection_config.curated)
|
|
backend_config = collection_config.backend
|
|
if backend_config.type.startswith('sqlite'):
|
|
backend = SQLiteBackend(curated_path / sqlite_db_filename)
|
|
else:
|
|
backend = RecordDirStore(
|
|
curated_path,
|
|
pid_mapping_function=get_mapping_function_by_name(
|
|
backend_config.mapping_method,
|
|
),
|
|
suffix='yaml',
|
|
)
|
|
pydantic_module = get_model_for_schema(collection_config.schema_location)[0]
|
|
add_records_to_backend(
|
|
backend,
|
|
pydantic_module,
|
|
g_default_entries[collection_config.name],
|
|
)
|
|
return test_client, store_path, admin_token
|
|
|
|
|
|
def add_records_to_backend(
|
|
backend: StorageBackend,
|
|
pydantic_module: ModuleType,
|
|
record_infos: list[tuple[str, str, str]],
|
|
):
|
|
for class_name, record_pid, yaml_stream in record_infos:
|
|
json_object = yaml.load(yaml_stream, Loader=yaml.SafeLoader )
|
|
assert record_pid == json_object['pid']
|
|
backend.add_record(
|
|
iri=resolve_curie(pydantic_module, json_object['pid']),
|
|
class_name=class_name,
|
|
json_object=json_object,
|
|
)
|