Skip to content
Snippets Groups Projects
test_classifications.py 31.52 KiB
# -*- coding: utf-8 -*-
import json
from uuid import UUID, uuid4

import pytest
from apistar.exceptions import ErrorResponse

from arkindex_worker.cache import CachedClassification, CachedElement
from arkindex_worker.models import Element

from . import BASE_API_CALLS


def test_get_ml_class_id_load_classes(responses, mock_elements_worker):
    corpus_id = "11111111-1111-1111-1111-111111111111"
    responses.add(
        responses.GET,
        f"http://testserver/api/v1/corpus/{corpus_id}/classes/",
        status=200,
        json={
            "count": 1,
            "next": None,
            "results": [
                {
                    "id": "0000",
                    "name": "good",
                }
            ],
        },
    )

    assert not mock_elements_worker.classes
    ml_class_id = mock_elements_worker.get_ml_class_id("good")

    assert len(responses.calls) == len(BASE_API_CALLS) + 1
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        (
            "GET",
            f"http://testserver/api/v1/corpus/{corpus_id}/classes/",
        ),
    ]
    assert mock_elements_worker.classes == {"good": "0000"}
    assert ml_class_id == "0000"


def test_get_ml_class_id_inexistant_class(mock_elements_worker, responses):
    # A missing class is now created automatically
    corpus_id = "11111111-1111-1111-1111-111111111111"
    mock_elements_worker.classes = {"good": "0000"}

    responses.add(
        responses.POST,
        f"http://testserver/api/v1/corpus/{corpus_id}/classes/",
        status=201,
        json={"id": "new-ml-class-1234"},
    )

    # Missing class at first
    assert mock_elements_worker.classes == {"good": "0000"}

    ml_class_id = mock_elements_worker.get_ml_class_id("bad")
    assert ml_class_id == "new-ml-class-1234"

    # Now it's available
    assert mock_elements_worker.classes == {
        "good": "0000",
        "bad": "new-ml-class-1234",
    }


def test_get_ml_class_id(mock_elements_worker):
    mock_elements_worker.classes = {"good": "0000"}

    ml_class_id = mock_elements_worker.get_ml_class_id("good")
    assert ml_class_id == "0000"


def test_get_ml_class_reload(responses, mock_elements_worker):
    corpus_id = "11111111-1111-1111-1111-111111111111"

    # Add some initial classes
    responses.add(
        responses.GET,
        f"http://testserver/api/v1/corpus/{corpus_id}/classes/",
        json={
            "count": 1,
            "next": None,
            "results": [
                {
                    "id": "class1_id",
                    "name": "class1",
                }
            ],
        },
    )

    # Invalid response when trying to create class2
    responses.add(
        responses.POST,
        f"http://testserver/api/v1/corpus/{corpus_id}/classes/",
        status=400,
        json={"non_field_errors": "Already exists"},
    )

    # Add both classes (class2 is created by another process)
    responses.add(
        responses.GET,
        f"http://testserver/api/v1/corpus/{corpus_id}/classes/",
        json={
            "count": 2,
            "next": None,
            "results": [
                {
                    "id": "class1_id",
                    "name": "class1",
                },
                {
                    "id": "class2_id",
                    "name": "class2",
                },
            ],
        },
    )

    # Simply request class 2, it should be reloaded
    assert mock_elements_worker.get_ml_class_id("class2") == "class2_id"

    assert len(responses.calls) == len(BASE_API_CALLS) + 3
    assert mock_elements_worker.classes == {
        "class1": "class1_id",
        "class2": "class2_id",
    }
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        (
            "GET",
            f"http://testserver/api/v1/corpus/{corpus_id}/classes/",
        ),
        (
            "POST",
            f"http://testserver/api/v1/corpus/{corpus_id}/classes/",
        ),
        (
            "GET",
            f"http://testserver/api/v1/corpus/{corpus_id}/classes/",
        ),
    ]


def test_retrieve_ml_class_in_cache(mock_elements_worker):
    """
    Look for a class that exists in cache -> No API Call
    """
    mock_elements_worker.classes = {"class1": "uuid1"}

    assert mock_elements_worker.retrieve_ml_class("uuid1") == "class1"


def test_retrieve_ml_class_not_in_cache(responses, mock_elements_worker):
    """
    Retrieve class not in cache -> Retrieve corpus ml classes via API
    """
    responses.add(
        responses.GET,
        f"http://testserver/api/v1/corpus/{mock_elements_worker.corpus_id}/classes/",
        status=200,
        json={
            "count": 1,
            "next": None,
            "results": [
                {
                    "id": "uuid1",
                    "name": "class1",
                },
            ],
        },
    )
    assert mock_elements_worker.retrieve_ml_class("uuid1") == "class1"
    assert len(responses.calls) == len(BASE_API_CALLS) + 1
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        (
            "GET",
            f"http://testserver/api/v1/corpus/{mock_elements_worker.corpus_id}/classes/",
        ),
    ]


def test_create_classification_wrong_element(mock_elements_worker):
    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classification(
            element=None,
            ml_class="a_class",
            confidence=0.42,
            high_confidence=True,
        )
    assert (
        str(e.value)
        == "element shouldn't be null and should be an Element or CachedElement"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classification(
            element="not element type",
            ml_class="a_class",
            confidence=0.42,
            high_confidence=True,
        )
    assert (
        str(e.value)
        == "element shouldn't be null and should be an Element or CachedElement"
    )


def test_create_classification_wrong_ml_class(mock_elements_worker, responses):
    elt = Element({"id": "12341234-1234-1234-1234-123412341234"})

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classification(
            element=elt,
            ml_class=None,
            confidence=0.42,
            high_confidence=True,
        )
    assert str(e.value) == "ml_class shouldn't be null and should be of type str"

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classification(
            element=elt,
            ml_class=1234,
            confidence=0.42,
            high_confidence=True,
        )
    assert str(e.value) == "ml_class shouldn't be null and should be of type str"

    # Automatically create a missing class !
    responses.add(
        responses.POST,
        "http://testserver/api/v1/corpus/11111111-1111-1111-1111-111111111111/classes/",
        status=201,
        json={"id": "new-ml-class-1234"},
    )
    responses.add(
        responses.POST,
        "http://testserver/api/v1/classifications/",
        status=201,
        json={"id": "new-classification-1234"},
    )
    mock_elements_worker.classes = {"another_class": "0000"}
    mock_elements_worker.create_classification(
        element=elt,
        ml_class="a_class",
        confidence=0.42,
        high_confidence=True,
    )

    # Check a class & classification has been created
    for call in responses.calls:
        print(call.request.url, call.request.body)

    assert [
        (call.request.url, json.loads(call.request.body))
        for call in responses.calls[-2:]
    ] == [
        (
            "http://testserver/api/v1/corpus/11111111-1111-1111-1111-111111111111/classes/",
            {"name": "a_class"},
        ),
        (
            "http://testserver/api/v1/classifications/",
            {
                "element": "12341234-1234-1234-1234-123412341234",
                "ml_class": "new-ml-class-1234",
                "worker_run_id": "56785678-5678-5678-5678-567856785678",
                "confidence": 0.42,
                "high_confidence": True,
            },
        ),
    ]


def test_create_classification_wrong_confidence(mock_elements_worker):
    mock_elements_worker.classes = {"a_class": "0000"}
    elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classification(
            element=elt,
            ml_class="a_class",
            confidence=None,
            high_confidence=True,
        )
    assert (
        str(e.value)
        == "confidence shouldn't be null and should be a float in [0..1] range"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classification(
            element=elt,
            ml_class="a_class",
            confidence="wrong confidence",
            high_confidence=True,
        )
    assert (
        str(e.value)
        == "confidence shouldn't be null and should be a float in [0..1] range"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classification(
            element=elt,
            ml_class="a_class",
            confidence=0,
            high_confidence=True,
        )
    assert (
        str(e.value)
        == "confidence shouldn't be null and should be a float in [0..1] range"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classification(
            element=elt,
            ml_class="a_class",
            confidence=2.00,
            high_confidence=True,
        )
    assert (
        str(e.value)
        == "confidence shouldn't be null and should be a float in [0..1] range"
    )


def test_create_classification_wrong_high_confidence(mock_elements_worker):
    mock_elements_worker.classes = {"a_class": "0000"}
    elt = Element({"id": "12341234-1234-1234-1234-123412341234"})

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classification(
            element=elt,
            ml_class="a_class",
            confidence=0.42,
            high_confidence=None,
        )
    assert (
        str(e.value) == "high_confidence shouldn't be null and should be of type bool"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classification(
            element=elt,
            ml_class="a_class",
            confidence=0.42,
            high_confidence="wrong high_confidence",
        )
    assert (
        str(e.value) == "high_confidence shouldn't be null and should be of type bool"
    )


def test_create_classification_api_error(responses, mock_elements_worker):
    mock_elements_worker.classes = {"a_class": "0000"}
    elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
    responses.add(
        responses.POST,
        "http://testserver/api/v1/classifications/",
        status=500,
    )

    with pytest.raises(ErrorResponse):
        mock_elements_worker.create_classification(
            element=elt,
            ml_class="a_class",
            confidence=0.42,
            high_confidence=True,
        )

    assert len(responses.calls) == len(BASE_API_CALLS) + 5
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        # We retry 5 times the API call
        ("POST", "http://testserver/api/v1/classifications/"),
        ("POST", "http://testserver/api/v1/classifications/"),
        ("POST", "http://testserver/api/v1/classifications/"),
        ("POST", "http://testserver/api/v1/classifications/"),
        ("POST", "http://testserver/api/v1/classifications/"),
    ]


def test_create_classification(responses, mock_elements_worker):
    mock_elements_worker.classes = {"a_class": "0000"}
    elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
    responses.add(
        responses.POST,
        "http://testserver/api/v1/classifications/",
        status=200,
    )

    mock_elements_worker.create_classification(
        element=elt,
        ml_class="a_class",
        confidence=0.42,
        high_confidence=True,
    )

    assert len(responses.calls) == len(BASE_API_CALLS) + 1
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        ("POST", "http://testserver/api/v1/classifications/"),
    ]

    assert json.loads(responses.calls[-1].request.body) == {
        "element": "12341234-1234-1234-1234-123412341234",
        "ml_class": "0000",
        "worker_run_id": "56785678-5678-5678-5678-567856785678",
        "confidence": 0.42,
        "high_confidence": True,
    }


def test_create_classification_with_cache(responses, mock_elements_worker_with_cache):
    mock_elements_worker_with_cache.classes = {"a_class": "0000"}
    elt = CachedElement.create(id="12341234-1234-1234-1234-123412341234", type="thing")

    responses.add(
        responses.POST,
        "http://testserver/api/v1/classifications/",
        status=200,
        json={
            "id": "56785678-5678-5678-5678-567856785678",
            "element": "12341234-1234-1234-1234-123412341234",
            "ml_class": "0000",
            "worker_run_id": "56785678-5678-5678-5678-567856785678",
            "confidence": 0.42,
            "high_confidence": True,
            "state": "pending",
        },
    )

    mock_elements_worker_with_cache.create_classification(
        element=elt,
        ml_class="a_class",
        confidence=0.42,
        high_confidence=True,
    )

    assert len(responses.calls) == len(BASE_API_CALLS) + 1
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        ("POST", "http://testserver/api/v1/classifications/"),
    ]

    assert json.loads(responses.calls[-1].request.body) == {
        "element": "12341234-1234-1234-1234-123412341234",
        "ml_class": "0000",
        "worker_run_id": "56785678-5678-5678-5678-567856785678",
        "confidence": 0.42,
        "high_confidence": True,
    }

    # Check that created classification was properly stored in SQLite cache
    assert list(CachedClassification.select()) == [
        CachedClassification(
            id=UUID("56785678-5678-5678-5678-567856785678"),
            element_id=UUID(elt.id),
            class_name="a_class",
            confidence=0.42,
            state="pending",
            worker_run_id=UUID("56785678-5678-5678-5678-567856785678"),
        )
    ]


def test_create_classification_duplicate_worker_run(responses, mock_elements_worker):
    mock_elements_worker.classes = {"a_class": "0000"}
    elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
    responses.add(
        responses.POST,
        "http://testserver/api/v1/classifications/",
        status=400,
        json={
            "non_field_errors": [
                "The fields element, worker_run, ml_class must make a unique set."
            ]
        },
    )

    mock_elements_worker.create_classification(
        element=elt,
        ml_class="a_class",
        confidence=0.42,
        high_confidence=True,
    )

    assert len(responses.calls) == len(BASE_API_CALLS) + 1
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        ("POST", "http://testserver/api/v1/classifications/"),
    ]

    assert json.loads(responses.calls[-1].request.body) == {
        "element": "12341234-1234-1234-1234-123412341234",
        "ml_class": "0000",
        "worker_run_id": "56785678-5678-5678-5678-567856785678",
        "confidence": 0.42,
        "high_confidence": True,
    }


def test_create_classifications_wrong_element(mock_elements_worker):
    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element=None,
            classifications=[
                {
                    "ml_class_id": "uuid1",
                    "confidence": 0.75,
                    "high_confidence": False,
                },
                {
                    "ml_class_id": "uuid2",
                    "confidence": 0.25,
                    "high_confidence": False,
                },
            ],
        )
    assert (
        str(e.value)
        == "element shouldn't be null and should be an Element or CachedElement"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element="not element type",
            classifications=[
                {
                    "ml_class_id": "uuid1",
                    "confidence": 0.75,
                    "high_confidence": False,
                },
                {
                    "ml_class_id": "uuid2",
                    "confidence": 0.25,
                    "high_confidence": False,
                },
            ],
        )
    assert (
        str(e.value)
        == "element shouldn't be null and should be an Element or CachedElement"
    )


def test_create_classifications_wrong_classifications(mock_elements_worker):
    elt = Element({"id": "12341234-1234-1234-1234-123412341234"})

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element=elt,
            classifications=None,
        )
    assert (
        str(e.value) == "classifications shouldn't be null and should be of type list"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element=elt,
            classifications=1234,
        )
    assert (
        str(e.value) == "classifications shouldn't be null and should be of type list"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element=elt,
            classifications=[
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 0.75,
                    "high_confidence": False,
                },
                {
                    "ml_class_id": 0.25,
                    "high_confidence": False,
                },
            ],
        )
    assert (
        str(e.value)
        == "Classification at index 1 in classifications: ml_class_id shouldn't be null and should be of type str"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element=elt,
            classifications=[
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 0.75,
                    "high_confidence": False,
                },
                {
                    "ml_class_id": None,
                    "confidence": 0.25,
                    "high_confidence": False,
                },
            ],
        )
    assert (
        str(e.value)
        == "Classification at index 1 in classifications: ml_class_id shouldn't be null and should be of type str"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element=elt,
            classifications=[
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 0.75,
                    "high_confidence": False,
                },
                {
                    "ml_class_id": 1234,
                    "confidence": 0.25,
                    "high_confidence": False,
                },
            ],
        )
    assert (
        str(e.value)
        == "Classification at index 1 in classifications: ml_class_id shouldn't be null and should be of type str"
    )

    with pytest.raises(ValueError) as e:
        mock_elements_worker.create_classifications(
            element=elt,
            classifications=[
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 0.75,
                    "high_confidence": False,
                },
                {
                    "ml_class_id": "not_an_uuid",
                    "confidence": 0.25,
                    "high_confidence": False,
                },
            ],
        )
    assert (
        str(e.value)
        == "Classification at index 1 in classifications: ml_class_id is not a valid uuid."
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element=elt,
            classifications=[
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 0.75,
                    "high_confidence": False,
                },
                {
                    "ml_class_id": str(uuid4()),
                    "high_confidence": False,
                },
            ],
        )
    assert (
        str(e.value)
        == "Classification at index 1 in classifications: confidence shouldn't be null and should be a float in [0..1] range"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element=elt,
            classifications=[
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 0.75,
                    "high_confidence": False,
                },
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": None,
                    "high_confidence": False,
                },
            ],
        )
    assert (
        str(e.value)
        == "Classification at index 1 in classifications: confidence shouldn't be null and should be a float in [0..1] range"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element=elt,
            classifications=[
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 0.75,
                    "high_confidence": False,
                },
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": "wrong confidence",
                    "high_confidence": False,
                },
            ],
        )
    assert (
        str(e.value)
        == "Classification at index 1 in classifications: confidence shouldn't be null and should be a float in [0..1] range"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element=elt,
            classifications=[
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 0.75,
                    "high_confidence": False,
                },
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 0,
                    "high_confidence": False,
                },
            ],
        )
    assert (
        str(e.value)
        == "Classification at index 1 in classifications: confidence shouldn't be null and should be a float in [0..1] range"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element=elt,
            classifications=[
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 0.75,
                    "high_confidence": False,
                },
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 2.00,
                    "high_confidence": False,
                },
            ],
        )
    assert (
        str(e.value)
        == "Classification at index 1 in classifications: confidence shouldn't be null and should be a float in [0..1] range"
    )

    with pytest.raises(AssertionError) as e:
        mock_elements_worker.create_classifications(
            element=elt,
            classifications=[
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 0.75,
                    "high_confidence": False,
                },
                {
                    "ml_class_id": str(uuid4()),
                    "confidence": 0.25,
                    "high_confidence": "wrong high_confidence",
                },
            ],
        )
    assert (
        str(e.value)
        == "Classification at index 1 in classifications: high_confidence should be of type bool"
    )


def test_create_classifications_api_error(responses, mock_elements_worker):
    responses.add(
        responses.POST,
        "http://testserver/api/v1/classification/bulk/",
        status=500,
    )
    elt = Element({"id": "12341234-1234-1234-1234-123412341234"})
    classes = [
        {
            "ml_class_id": str(uuid4()),
            "confidence": 0.75,
            "high_confidence": False,
        },
        {
            "ml_class_id": str(uuid4()),
            "confidence": 0.25,
            "high_confidence": False,
        },
    ]

    with pytest.raises(ErrorResponse):
        mock_elements_worker.create_classifications(
            element=elt, classifications=classes
        )

    assert len(responses.calls) == len(BASE_API_CALLS) + 5
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        # We retry 5 times the API call
        ("POST", "http://testserver/api/v1/classification/bulk/"),
        ("POST", "http://testserver/api/v1/classification/bulk/"),
        ("POST", "http://testserver/api/v1/classification/bulk/"),
        ("POST", "http://testserver/api/v1/classification/bulk/"),
        ("POST", "http://testserver/api/v1/classification/bulk/"),
    ]


def test_create_classifications(responses, mock_elements_worker_with_cache):
    # Set MLClass in cache
    portrait_uuid = str(uuid4())
    landscape_uuid = str(uuid4())
    mock_elements_worker_with_cache.classes = {
        "portrait": portrait_uuid,
        "landscape": landscape_uuid,
    }

    elt = CachedElement.create(id="12341234-1234-1234-1234-123412341234", type="thing")
    classes = [
        {
            "ml_class_id": portrait_uuid,
            "confidence": 0.75,
            "high_confidence": False,
        },
        {
            "ml_class_id": landscape_uuid,
            "confidence": 0.25,
            "high_confidence": False,
        },
    ]

    responses.add(
        responses.POST,
        "http://testserver/api/v1/classification/bulk/",
        status=200,
        json={
            "parent": str(elt.id),
            "worker_run_id": "56785678-5678-5678-5678-567856785678",
            "classifications": [
                {
                    "id": "00000000-0000-0000-0000-000000000000",
                    "ml_class": portrait_uuid,
                    "confidence": 0.75,
                    "high_confidence": False,
                    "state": "pending",
                },
                {
                    "id": "11111111-1111-1111-1111-111111111111",
                    "ml_class": landscape_uuid,
                    "confidence": 0.25,
                    "high_confidence": False,
                    "state": "pending",
                },
            ],
        },
    )

    mock_elements_worker_with_cache.create_classifications(
        element=elt, classifications=classes
    )

    assert len(responses.calls) == len(BASE_API_CALLS) + 1
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        ("POST", "http://testserver/api/v1/classification/bulk/"),
    ]

    assert json.loads(responses.calls[-1].request.body) == {
        "parent": str(elt.id),
        "worker_run_id": "56785678-5678-5678-5678-567856785678",
        "classifications": classes,
    }

    # Check that created classifications were properly stored in SQLite cache
    assert list(CachedClassification.select()) == [
        CachedClassification(
            id=UUID("00000000-0000-0000-0000-000000000000"),
            element_id=UUID(elt.id),
            class_name="portrait",
            confidence=0.75,
            state="pending",
            worker_run_id=UUID("56785678-5678-5678-5678-567856785678"),
        ),
        CachedClassification(
            id=UUID("11111111-1111-1111-1111-111111111111"),
            element_id=UUID(elt.id),
            class_name="landscape",
            confidence=0.25,
            state="pending",
            worker_run_id=UUID("56785678-5678-5678-5678-567856785678"),
        ),
    ]


def test_create_classifications_not_in_cache(
    responses, mock_elements_worker_with_cache
):
    """
    CreateClassifications using ID that are not in `.classes` attribute.
    Will load corpus MLClass to insert the corresponding name in Cache.
    """
    portrait_uuid = str(uuid4())
    landscape_uuid = str(uuid4())
    elt = CachedElement.create(id="12341234-1234-1234-1234-123412341234", type="thing")
    classes = [
        {
            "ml_class_id": portrait_uuid,
            "confidence": 0.75,
            "high_confidence": False,
        },
        {
            "ml_class_id": landscape_uuid,
            "confidence": 0.25,
            "high_confidence": False,
        },
    ]

    responses.add(
        responses.POST,
        "http://testserver/api/v1/classification/bulk/",
        status=200,
        json={
            "parent": str(elt.id),
            "worker_run_id": "56785678-5678-5678-5678-567856785678",
            "classifications": [
                {
                    "id": "00000000-0000-0000-0000-000000000000",
                    "ml_class": portrait_uuid,
                    "confidence": 0.75,
                    "high_confidence": False,
                    "state": "pending",
                },
                {
                    "id": "11111111-1111-1111-1111-111111111111",
                    "ml_class": landscape_uuid,
                    "confidence": 0.25,
                    "high_confidence": False,
                    "state": "pending",
                },
            ],
        },
    )
    responses.add(
        responses.GET,
        f"http://testserver/api/v1/corpus/{mock_elements_worker_with_cache.corpus_id}/classes/",
        status=200,
        json={
            "count": 2,
            "next": None,
            "results": [
                {
                    "id": portrait_uuid,
                    "name": "portrait",
                },
                {"id": landscape_uuid, "name": "landscape"},
            ],
        },
    )

    mock_elements_worker_with_cache.create_classifications(
        element=elt, classifications=classes
    )

    assert len(responses.calls) == len(BASE_API_CALLS) + 2
    assert [
        (call.request.method, call.request.url) for call in responses.calls
    ] == BASE_API_CALLS + [
        ("POST", "http://testserver/api/v1/classification/bulk/"),
        (
            "GET",
            f"http://testserver/api/v1/corpus/{mock_elements_worker_with_cache.corpus_id}/classes/",
        ),
    ]

    assert json.loads(responses.calls[-2].request.body) == {
        "parent": str(elt.id),
        "worker_run_id": "56785678-5678-5678-5678-567856785678",
        "classifications": classes,
    }

    # Check that created classifications were properly stored in SQLite cache
    assert list(CachedClassification.select()) == [
        CachedClassification(
            id=UUID("00000000-0000-0000-0000-000000000000"),
            element_id=UUID(elt.id),
            class_name="portrait",
            confidence=0.75,
            state="pending",
            worker_run_id=UUID("56785678-5678-5678-5678-567856785678"),
        ),
        CachedClassification(
            id=UUID("11111111-1111-1111-1111-111111111111"),
            element_id=UUID(elt.id),
            class_name="landscape",
            confidence=0.25,
            state="pending",
            worker_run_id=UUID("56785678-5678-5678-5678-567856785678"),
        ),
    ]