Check a storage#

Use this guide to check that a storage follows the rules of ScimStorage, with the test suite scim2-server provides. It assumes a storage written as Write a storage explains, and tests run with pytest.

The suite checks what the method signatures do not tell: the copies, the 404 and 412 errors, the dates and versions, the uniqueness of userName, and the filters, sorting and paging of the searches. It runs each filter and each sort of a list, and compares the answer of the storage with the rules of SCIM. It uses the User and Group resource types of RFC 7643.

Run the suite#

  1. Install the testing extra, which installs pytest:

    $ pip install "scim2-server[testing]"
    
  2. In a test module, subclass ScimStorageContract, and give it a storage fixture that returns a new, empty storage. The provider fixture of the suite gives the description of the service, that the storage needs. For an AsyncScimStorage, subclass AsyncScimStorageContract instead, and name the fixture async_storage:

    import sqlite3
    
    import pytest
    from scim2_server.testing import ScimStorageContract
    
    from myapp.scim import SQLiteStorage
    
    
    class TestSQLiteStorage(ScimStorageContract):
        @pytest.fixture
        def storage(self, provider):
            connection = sqlite3.connect(":memory:")
            yield SQLiteStorage(connection, provider)
            connection.close()
    
    import asyncio
    
    import pytest
    from scim2_server.testing import AsyncScimStorageContract
    
    from myapp.scim import AsyncSQLiteStorage
    
    
    class TestAsyncSQLiteStorage(AsyncScimStorageContract):
        @pytest.fixture
        def async_storage(self, provider):
            storage = AsyncSQLiteStorage(":memory:", provider)
            yield storage
            asyncio.run(storage.close())
    

    The asynchronous suite runs the same tests. Each test runs the coroutines of the storage on its own event loop.

  3. Run pytest. Every test of the suite runs against the storage:

    $ pytest
    ........................................................................ [ 66%]
    ....................................                                     [100%]
    108 passed in 6.09s
    

Skip what the storage does not support#

The suite skips the tests of a feature that the service does not announce. pytest lists these tests as skipped, with the feature they need.

Describe the service as the server announces it. For a storage that cannot filter or sort, override the provider fixture. In its ServiceProviderConfig, set supported to False for filter or sort:

import sqlite3

import pytest
from myapp.scim import SQLiteStorage

from scim2_server.testing import ScimStorageContract
from scim2_server.utils import load_default_provider


class TestStorageWithoutFilterNorSort(ScimStorageContract):
    @pytest.fixture
    def storage(self, provider):
        connection = sqlite3.connect(":memory:")
        yield SQLiteStorage(connection, provider)
        connection.close()

    @pytest.fixture
    def provider(self):
        provider = load_default_provider()
        provider.config.filter.supported = False
        provider.config.sort.supported = False
        return provider
$ pytest -rs
SKIPPED [46] scim2_server/testing.py:216: The service does not support filtering
SKIPPED [19] scim2_server/testing.py:216: The service does not support sorting
SKIPPED [10] scim2_server/testing.py:216: The service does not support cursor pagination
======================= 33 passed, 75 skipped in 6.57s =========================

For a storage that cannot search several resource types at once, set supports_root_search to False:

import sqlite3

import pytest
from myapp.scim import SQLiteStorage

from scim2_server.testing import ScimStorageContract


class TestStorageWithoutRootSearch(ScimStorageContract):
    supports_root_search = False

    @pytest.fixture
    def storage(self, provider):
        connection = sqlite3.connect(":memory:")
        yield SQLiteStorage(connection, provider)
        connection.close()
$ pytest -rs
SKIPPED [2] scim2_server/testing.py:216: The service does not support searching at the root
SKIPPED [10] scim2_server/testing.py:216: The service does not support cursor pagination
======================= 96 passed, 12 skipped in 6.79s =========================

A storage refuses a filter it cannot translate with InvalidFilterException, and a sort with InvalidPathException. The suite skips the filters and the sorts the storage refuses, and checks the others:

$ pytest -rs
SKIPPED [11] scim2_server/testing.py:700: The storage refuses the search: Cannot filter on 'title'
SKIPPED [2] scim2_server/testing.py:700: The storage refuses the search: Cannot sort on 'title'

The filters and the sorts search users with the attributes of the User resource type and of its enterprise extension. For a storage that does not store some of them, override search_users(), and return the users without these attributes. The suite skips the filters and the sorts on the attributes the models of the provider fixture lack.